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分析才是重点,敬请期待!

相关推荐
菜包eo22 分钟前
二维码驱动的独立站视频集成方案
网络·python·音视频
即将头秃的程序媛32 分钟前
centos 7.9安装tomcat,并实现开机自启
linux·运维·centos
fangeqin41 分钟前
ubuntu源码安装python3.13遇到Could not build the ssl module!解决方法
linux·python·ubuntu·openssl
爱奥尼欧2 小时前
【Linux 系统】基础IO——Linux中对文件的理解
linux·服务器·microsoft
超喜欢下雨天2 小时前
服务器安装 ros2时遇到底层库依赖冲突的问题
linux·运维·服务器·ros2
阿蒙Amon3 小时前
【Python小工具】使用 OpenCV 获取视频时长的详细指南
python·opencv·音视频
tan77º3 小时前
【Linux网络编程】网络基础
linux·服务器·网络
笑衬人心。4 小时前
Ubuntu 22.04 + MySQL 8 无密码登录问题与 root 密码重置指南
linux·mysql·ubuntu
aqi005 小时前
FFmpeg开发笔记(七十一)使用国产的QPlayer2实现双播放器观看视频
android·ffmpeg·音视频·流媒体
chanalbert5 小时前
CentOS系统新手指导手册
linux·运维·centos