• live555 rtsp服务器实战之createNewStreamSource


    live555关于RTSP协议交互流程

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

    live555 rtsp服务器实战之createNewStreamSource

    live555搭建实时播放rtsp服务器

    live555 rtsp服务器实战之doGetNextFrame

    概述

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

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

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

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

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

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

            H264LiveVideoServerMediaSubssion:OnDemandServerMediaSubsession:ServerMediaSubsession:Medium


    createNewStreamSource

    本章介绍createNewStreamSource函数;

            虚函数createNewStreamSource声明于OnDemandServerMediaSubsession类:

    1. virtual FramedSource* createNewStreamSource(unsigned clientSessionId,
    2. unsigned& estBitrate) = 0;

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

            因此H264LiveVideoServerMediaSubssion类需要继承OnDemandServerMediaSubsession类;

            createNewStreamSource函数实现解析:

    1. FramedSource* H264LiveVideoServerMediaSubssion::createNewStreamSource(unsigned clientSessionId, unsigned& estBitrate)
    2. {
    3. /* Remain to do : assign estBitrate */
    4. estBitrate = 1000; // kbps, estimate
    5. //创建视频源
    6. H264FramedLiveSource* liveSource = H264FramedLiveSource::createNew(envir(), Server_datasize, Server_databuf, Server_dosent);
    7. if (liveSource == NULL)
    8. {
    9. return NULL;
    10. }
    11. // Create a framer for the Video Elementary Stream:
    12. return H264VideoStreamFramer::createNew(envir(), liveSource);
    13. }

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

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

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

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

    1. void RTSPServer::RTSPClientConnection::handleRequestBytes(int newBytesRead)
    2. {
    3. .
    4. .
    5. .
    6. if (urlIsRTSPS != fOurRTSPServer.fOurConnectionsUseTLS)
    7. {
    8. #ifdef DEBUG
    9. fprintf(stderr, "Calling handleCmd_redirect()\n");
    10. #endif
    11. handleCmd_redirect(urlSuffix);
    12. }
    13. else if (strcmp(cmdName, "OPTIONS") == 0)
    14. {
    15. // If the "OPTIONS" command included a "Session:" id for a session that doesn't exist,
    16. // then treat this as an error:
    17. if (requestIncludedSessionId && clientSession == NULL)
    18. {
    19. #ifdef DEBUG
    20. fprintf(stderr, "Calling handleCmd_sessionNotFound() (case 1)\n");
    21. #endif
    22. handleCmd_sessionNotFound();
    23. }
    24. else
    25. {
    26. // Normal case:
    27. handleCmd_OPTIONS();
    28. }
    29. }
    30. else if (urlPreSuffix[0] == '\0' && urlSuffix[0] == '*' && urlSuffix[1] == '\0')
    31. {
    32. // The special "*" URL means: an operation on the entire server. This works only for GET_PARAMETER and SET_PARAMETER:
    33. if (strcmp(cmdName, "GET_PARAMETER") == 0)
    34. {
    35. handleCmd_GET_PARAMETER((char const *)fRequestBuffer);
    36. }
    37. else if (strcmp(cmdName, "SET_PARAMETER") == 0)
    38. {
    39. handleCmd_SET_PARAMETER((char const *)fRequestBuffer);
    40. }
    41. else
    42. {
    43. handleCmd_notSupported();
    44. }
    45. }
    46. else if (strcmp(cmdName, "DESCRIBE") == 0)
    47. {
    48. handleCmd_DESCRIBE(urlPreSuffix, urlSuffix, (char const *)fRequestBuffer);
    49. }
    50. else if (strcmp(cmdName, "SETUP") == 0)
    51. {
    52. Boolean areAuthenticated = True;
    53. if (!requestIncludedSessionId)
    54. {
    55. // No session id was present in the request.
    56. // So create a new "RTSPClientSession" object for this request.
    57. // But first, make sure that we're authenticated to perform this command:
    58. char urlTotalSuffix[2 * RTSP_PARAM_STRING_MAX];
    59. // enough space for urlPreSuffix/urlSuffix'\0'
    60. urlTotalSuffix[0] = '\0';
    61. if (urlPreSuffix[0] != '\0')
    62. {
    63. strcat(urlTotalSuffix, urlPreSuffix);
    64. strcat(urlTotalSuffix, "/");
    65. }
    66. strcat(urlTotalSuffix, urlSuffix);
    67. if (authenticationOK("SETUP", urlTotalSuffix, (char const *)fRequestBuffer))
    68. {
    69. clientSession = (RTSPServer::RTSPClientSession *)fOurRTSPServer.createNewClientSessionWithId();
    70. }
    71. else
    72. {
    73. areAuthenticated = False;
    74. }
    75. }
    76. if (clientSession != NULL)
    77. {
    78. clientSession->handleCmd_SETUP(this, urlPreSuffix, urlSuffix, (char const *)fRequestBuffer);
    79. playAfterSetup = clientSession->fStreamAfterSETUP;
    80. }
    81. else if (areAuthenticated)
    82. {
    83. #ifdef DEBUG
    84. fprintf(stderr, "Calling handleCmd_sessionNotFound() (case 2)\n");
    85. #endif
    86. handleCmd_sessionNotFound();
    87. }
    88. }
    89. else if (strcmp(cmdName, "TEARDOWN") == 0 || strcmp(cmdName, "PLAY") == 0 || strcmp(cmdName, "PAUSE") == 0 || strcmp(cmdName, "GET_PARAMETER") == 0 || strcmp(cmdName, "SET_PARAMETER") == 0)
    90. {
    91. if (clientSession != NULL)
    92. {
    93. clientSession->handleCmd_withinSession(this, cmdName, urlPreSuffix, urlSuffix, (char const *)fRequestBuffer);
    94. }
    95. else
    96. {
    97. #ifdef DEBUG
    98. fprintf(stderr, "Calling handleCmd_sessionNotFound() (case 3)\n");
    99. #endif
    100. handleCmd_sessionNotFound();
    101. }
    102. }
    103. else if (strcmp(cmdName, "REGISTER") == 0 || strcmp(cmdName, "DEREGISTER") == 0)
    104. {
    105. // Because - unlike other commands - an implementation of this command needs
    106. // the entire URL, we re-parse the command to get it:
    107. char *url = strDupSize((char *)fRequestBuffer);
    108. if (sscanf((char *)fRequestBuffer, "%*s %s", url) == 1)
    109. {
    110. // Check for special command-specific parameters in a "Transport:" header:
    111. Boolean reuseConnection, deliverViaTCP;
    112. char *proxyURLSuffix;
    113. parseTransportHeaderForREGISTER((const char *)fRequestBuffer, reuseConnection, deliverViaTCP, proxyURLSuffix);
    114. handleCmd_REGISTER(cmdName, url, urlSuffix, (char const *)fRequestBuffer, reuseConnection, deliverViaTCP, proxyURLSuffix);
    115. delete[] proxyURLSuffix;
    116. }
    117. else
    118. {
    119. handleCmd_bad();
    120. }
    121. delete[] url;
    122. }
    123. else
    124. {
    125. // The command is one that we don't handle:
    126. handleCmd_notSupported();
    127. }
    128. .
    129. .
    130. .
    131. }

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

    1. void OnDemandServerMediaSubsession ::getStreamParameters(unsigned clientSessionId,
    2. struct sockaddr_storage const &clientAddress,
    3. Port const &clientRTPPort,
    4. Port const &clientRTCPPort,
    5. int tcpSocketNum,
    6. unsigned char rtpChannelId,
    7. unsigned char rtcpChannelId,
    8. TLSState *tlsState,
    9. struct sockaddr_storage &destinationAddress,
    10. u_int8_t & /*destinationTTL*/,
    11. Boolean &isMulticast,
    12. Port &serverRTPPort,
    13. Port &serverRTCPPort,
    14. void *&streamToken)
    15. {
    16. if (addressIsNull(destinationAddress))
    17. {
    18. // normal case - use the client address as the destination address:
    19. destinationAddress = clientAddress;
    20. }
    21. isMulticast = False;
    22. if (fLastStreamToken != NULL && fReuseFirstSource)
    23. {
    24. // Special case: Rather than creating a new 'StreamState',
    25. // we reuse the one that we've already created:
    26. serverRTPPort = ((StreamState *)fLastStreamToken)->serverRTPPort();
    27. serverRTCPPort = ((StreamState *)fLastStreamToken)->serverRTCPPort();
    28. ++((StreamState *)fLastStreamToken)->referenceCount();
    29. streamToken = fLastStreamToken;
    30. }
    31. else
    32. {
    33. // Normal case: Create a new media source:
    34. unsigned streamBitrate;
    35. FramedSource *mediaSource = createNewStreamSource(clientSessionId, streamBitrate);
    36. .
    37. .
    38. .
    39. }
    40. .
    41. .
    42. .
    43. }

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

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

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

  • 相关阅读:
    IDL学习——外部方法调用IDL.pro文件
    vue、全局前置守卫
    Java异步编排CompletableFuture简述
    指针进阶(3)
    【MySQL | 进阶篇】06、全局锁、表级锁、行级锁
    函数式编程Haskell初探
    深度学习模型调参经验
    番茄小说推文怎么通过巨量推文进行授权
    进阶JS-filter用法
    【SpringCloud微服务--Eureka服务注册中心】
  • 原文地址:https://blog.csdn.net/qq_39466755/article/details/140438101