live555 rtsp服务器实战之createNewStreamSource

live555关于RTSP协议交互流程

live555的核心数据结构值之闭环双向链表

live555 rtsp服务器实战之createNewStreamSource

概述

live555用于实际项目开发时,createNewStreamSource和doGetNextFrame是必须要实现的两个虚函数,一般会创建两个类来实现这两个函数:假如这两个类为H264LiveVideoServerMediaSubssion和H264FramedLiveSource;

H264LiveVideoServerMediaSubssion为实时视频会话类,用于实现createNewStreamSource虚函数;

H264FramedLiveSource为实时视频帧资源类,用户实现doGetNextFrame函数;

那么这两个函数是什么时候被调用以及他们的作用是什么呢?本节将详细介绍;

声明:该文章基于H264视频源为基础分析,其他源类似;

由于这两个类主要是自定义虚函数,所以存在一定的继承关系,首先需要明确这两个类的继承关系(本章只介绍H264LiveVideoServerMediaSubssion):

H264LiveVideoServerMediaSubssion:OnDemandServerMediaSubsession:ServerMediaSubsession:Medium


createNewStreamSource

本章介绍createNewStreamSource函数;

虚函数createNewStreamSource声明于OnDemandServerMediaSubsession类:

cpp 复制代码
virtual FramedSource* createNewStreamSource(unsigned clientSessionId,
                unsigned& estBitrate) = 0;

可以看出声明时为纯虚函数(live555提供的对外接口,用户自定义实现),必须有子类对该函数进行实现;该函数用于获取媒体流源的资源对象,简单点说就是指明服务器调用哪个doGetNextFrame函数获取视频流;

因此H264LiveVideoServerMediaSubssion类需要继承OnDemandServerMediaSubsession类;

createNewStreamSource函数实现解析:

cpp 复制代码
FramedSource* H264LiveVideoServerMediaSubssion::createNewStreamSource(unsigned clientSessionId, unsigned& estBitrate)
{
    /* Remain to do : assign estBitrate */
    estBitrate = 1000; // kbps, estimate

    //创建视频源
    H264FramedLiveSource* liveSource = H264FramedLiveSource::createNew(envir(), Server_datasize, Server_databuf, Server_dosent);
    if (liveSource == NULL)
    {
        return NULL;
    }

    // Create a framer for the Video Elementary Stream:
    return H264VideoStreamFramer::createNew(envir(), liveSource);
}

上述代码就是最基础的虚函数createNewStreamSource的实现,其中H264FramedLiveSource就是视频源资源的类;也就是获取视频帧的函数doGetNextFrame所在的类;该函数主要做了两件事:

  1. 创建h264帧资源类对象liveSource;目的是获取调用doGetNextFrame的方法;

  2. 返回H264VideoStreamFramer类对象,并将h264帧资源类对象传递进去,最终liveSource赋值给StreamParser类中成员变量fInputSource和FramedFilter类的成员变量fInputSource;

该函数在SETUP信令交互时期被调用,首先看一下信令处理函数handleRequestBytes:

cpp 复制代码
void RTSPServer::RTSPClientConnection::handleRequestBytes(int newBytesRead)
{
    .
    .
    .
    if (urlIsRTSPS != fOurRTSPServer.fOurConnectionsUseTLS)
      {
#ifdef DEBUG
        fprintf(stderr, "Calling handleCmd_redirect()\n");
#endif
        handleCmd_redirect(urlSuffix);
      }
      else if (strcmp(cmdName, "OPTIONS") == 0)
      {
        // If the "OPTIONS" command included a "Session:" id for a session that doesn't exist,
        // then treat this as an error:
        if (requestIncludedSessionId && clientSession == NULL)
        {
#ifdef DEBUG
          fprintf(stderr, "Calling handleCmd_sessionNotFound() (case 1)\n");
#endif
          handleCmd_sessionNotFound();
        }
        else
        {
          // Normal case:
          handleCmd_OPTIONS();
        }
      }
      else if (urlPreSuffix[0] == '\0' && urlSuffix[0] == '*' && urlSuffix[1] == '\0')
      {
        // The special "*" URL means: an operation on the entire server.  This works only for GET_PARAMETER and SET_PARAMETER:
        if (strcmp(cmdName, "GET_PARAMETER") == 0)
        {
          handleCmd_GET_PARAMETER((char const *)fRequestBuffer);
        }
        else if (strcmp(cmdName, "SET_PARAMETER") == 0)
        {
          handleCmd_SET_PARAMETER((char const *)fRequestBuffer);
        }
        else
        {
          handleCmd_notSupported();
        }
      }
      else if (strcmp(cmdName, "DESCRIBE") == 0)
      {
        handleCmd_DESCRIBE(urlPreSuffix, urlSuffix, (char const *)fRequestBuffer);
      }
      else if (strcmp(cmdName, "SETUP") == 0)
      {
        Boolean areAuthenticated = True;

        if (!requestIncludedSessionId)
        {
          // No session id was present in the request.
          // So create a new "RTSPClientSession" object for this request.

          // But first, make sure that we're authenticated to perform this command:
          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("SETUP", urlTotalSuffix, (char const *)fRequestBuffer))
          {
            clientSession = (RTSPServer::RTSPClientSession *)fOurRTSPServer.createNewClientSessionWithId();
          }
          else
          {
            areAuthenticated = False;
          }
        }
        if (clientSession != NULL)
        {
          clientSession->handleCmd_SETUP(this, urlPreSuffix, urlSuffix, (char const *)fRequestBuffer);
          playAfterSetup = clientSession->fStreamAfterSETUP;
        }
        else if (areAuthenticated)
        {
#ifdef DEBUG
          fprintf(stderr, "Calling handleCmd_sessionNotFound() (case 2)\n");
#endif
          handleCmd_sessionNotFound();
        }
      }
      else if (strcmp(cmdName, "TEARDOWN") == 0 || strcmp(cmdName, "PLAY") == 0 || strcmp(cmdName, "PAUSE") == 0 || strcmp(cmdName, "GET_PARAMETER") == 0 || strcmp(cmdName, "SET_PARAMETER") == 0)
      {
        if (clientSession != NULL)
        {
          clientSession->handleCmd_withinSession(this, cmdName, urlPreSuffix, urlSuffix, (char const *)fRequestBuffer);
        }
        else
        {
#ifdef DEBUG
          fprintf(stderr, "Calling handleCmd_sessionNotFound() (case 3)\n");
#endif
          handleCmd_sessionNotFound();
        }
      }
      else if (strcmp(cmdName, "REGISTER") == 0 || strcmp(cmdName, "DEREGISTER") == 0)
      {
        // Because - unlike other commands - an implementation of this command needs
        // the entire URL, we re-parse the command to get it:
        char *url = strDupSize((char *)fRequestBuffer);
        if (sscanf((char *)fRequestBuffer, "%*s %s", url) == 1)
        {
          // Check for special command-specific parameters in a "Transport:" header:
          Boolean reuseConnection, deliverViaTCP;
          char *proxyURLSuffix;
          parseTransportHeaderForREGISTER((const char *)fRequestBuffer, reuseConnection, deliverViaTCP, proxyURLSuffix);

          handleCmd_REGISTER(cmdName, url, urlSuffix, (char const *)fRequestBuffer, reuseConnection, deliverViaTCP, proxyURLSuffix);
          delete[] proxyURLSuffix;
        }
        else
        {
          handleCmd_bad();
        }
        delete[] url;
      }
      else
      {
        // The command is one that we don't handle:
        handleCmd_notSupported();
      }
      .
      .
      .
}

handleRequestBytes函数是在doEventLoop主循环中监测的RTSP客户端有数据发送时调用的函数(关于rtsp的tcp udp协议交互,参考上面的文章),作用就是处理各种信令(OPTION DESCRIBE SETUP等)及数据;其中就调用了handleCmd_SETUP处理SETUP信令的函数;handleCmd_SETUP函数又调用了getStreamParameters函数:

cpp 复制代码
void OnDemandServerMediaSubsession ::getStreamParameters(unsigned clientSessionId,
                                                         struct sockaddr_storage const &clientAddress,
                                                         Port const &clientRTPPort,
                                                         Port const &clientRTCPPort,
                                                         int tcpSocketNum,
                                                         unsigned char rtpChannelId,
                                                         unsigned char rtcpChannelId,
                                                         TLSState *tlsState,
                                                         struct sockaddr_storage &destinationAddress,
                                                         u_int8_t & /*destinationTTL*/,
                                                         Boolean &isMulticast,
                                                         Port &serverRTPPort,
                                                         Port &serverRTCPPort,
                                                         void *&streamToken)
{
    if (addressIsNull(destinationAddress))
    {
        // normal case - use the client address as the destination address:
        destinationAddress = clientAddress;
    }
  isMulticast = False;

  if (fLastStreamToken != NULL && fReuseFirstSource)
  {
    // Special case: Rather than creating a new 'StreamState',
    // we reuse the one that we've already created:
    serverRTPPort = ((StreamState *)fLastStreamToken)->serverRTPPort();
    serverRTCPPort = ((StreamState *)fLastStreamToken)->serverRTCPPort();
    ++((StreamState *)fLastStreamToken)->referenceCount();
    streamToken = fLastStreamToken;
  }
  else
  {
    // Normal case: Create a new media source:
    unsigned streamBitrate;
    FramedSource *mediaSource = createNewStreamSource(clientSessionId, streamBitrate);
    .
    .
    .
  }
  .
  .
  .
}

该函数就在OnDemandServerMediaSubsession类,也就是createNewStreamSource实现类H264LiveVideoServerMediaSubssion的父类,这时调用的createNewStreamSource就是我们自己实现的逻辑啦!返回值mediaSource后续被用来调用doGetNextFrame函数获取视频帧使用!

可以看出这两件事的目的都是为了告诉rtsp服务器怎么获取视频帧数据;关于fInputSource变量什么时候被使用的,请参考我的下篇文章:未知地址!哈哈哈!

下节的doGetNextFrame分析才是重点,敬请期待!

相关推荐
远游客071339 分钟前
centos stream 8下载安装遇到的坑
linux·服务器·centos
马甲是掉不了一点的<.<40 分钟前
本地电脑使用命令行上传文件至远程服务器
linux·scp·cmd·远程文件上传
jingyu飞鸟41 分钟前
centos-stream9系统安装docker
linux·docker·centos
超爱吃士力架1 小时前
邀请逻辑
java·linux·后端
darkdragonking3 小时前
FLV视频封装格式详解
音视频
cominglately4 小时前
centos单机部署seata
linux·运维·centos
魏 无羡4 小时前
linux CentOS系统上卸载docker
linux·kubernetes·centos
CircleMouse4 小时前
Centos7, 使用yum工具,出现 Could not resolve host: mirrorlist.centos.org
linux·运维·服务器·centos
元争栈道4 小时前
webview和H5来实现的android短视频(短剧)音视频播放依赖控件
android·音视频
木子Linux5 小时前
【Linux打怪升级记 | 问题01】安装Linux系统忘记设置时区怎么办?3个方法教你回到东八区
linux·运维·服务器·centos·云计算