在之前的博客中,我们提到了如何用Darwin&live555实现直播过程,那么更进一步,当直播结束时,我们需要关闭所有正在收看的客户端,并且delete转发会话ReflectorSession,这样才能够在下一次再有同样名称的流推送进来时,创建新的转发Session。

我们下面所做的修改都是基于Darwin 6.0.3进行,具体下载可到https://github.com/xiejiashu/EasyDarwin获取。我们首先来对原有的ReflectorSession控制进行分析:

首先对于推送端,主要经历DoAnnounceDoSetupDoPlayProcessRTPData四个流程,在DoAnnounce时,QTSSReflectorModule并不创建ReflectorSession,所以此步跳过;

在第一次DoSetup推送流程时,转发会话ReflectorSession还没有创建,所以必然会进入FindOrCreateSession函数中的

  1. if (theSessionRef == NULL)
  2. {
  3. ... //创建ReflectorSession
  4. }

在此函数中,变量isPush为true,所以不会引用到

  1. if (!isPush)
  2. { OSRef* debug = sSessionMap->Resolve(inPath);
  3. Assert(debug == theSession->GetRef());
  4. }

所以,对ReflectorSession的引用在执行第一次DoSetup推送的时候为
+0.

并且此时,还会设置推送端RTPSession的sClientBroadcastSessionAttr属性

  1. theErr = QTSS_SetValue(inParams->inClientSession, sClientBroadcastSessionAttr, 0, &theSession, sizeof(theSession));

当第二次进入DoSetup推送流程时(如果有多个Track),同样进入到FindOrCreateSession时,ReflectorSession已经创建,所以直接 OSRef* theSessionRef = sSessionMap->Resolve(inPath);调用后,直接获取到第一次创建的ReflectorSession,引用数+1.

第N次调用DoSetup(有时候会有多个Track推送,如3D视频等),可以推算得到,引用数为
+(N-1).

在进入到DoPlay(QTSS_StandardRTSP_Params* inParams, ReflectorSession* inSession) 推送流程时,参数inSession为NULL,所以调用到过程

  1. // do all above so we can add the session to the map with Resolve here.
  2. // we must only do this once.
  3. OSRef* debug = sSessionMap->Resolve(&thePathPtr);
  4. if (debug != inSession->GetRef())
  5. {
  6. return QTSSModuleUtils::SendErrorResponse(inParams->inRTSPRequest, qtssClientBadRequest, 0);
  7. }

由上过程得知,DoPlay为ReflectorSession增加了一次引用,+1.

ProcessRTPData()过程并未进行sSessionMap的Resolve过程,所以此函数也可以跳过。如此一来,可以统计得到,推送端全程对ReflectorSession的引用数目为:N-1 + 1 =N(N为媒体的Track数目,对应到ReflectorSession中,也就是ReflectorStream的数量)。

再来看客户端,客户端经历DoDescribeDoSetupDoPlay三个流程。

客户端进入DoDescribe流程时,假设ReflectorSession已经由推送端建立,如此一来,同样是在FindOrCreateSession中,增加了一次对ReflectorSession的引用,+1.

当客户端第一次进入到DoSetup流程时

  1. RTPSessionOutput** theOutput = NULL;
  2. theErr = QTSS_GetValuePtr(inParams->inClientSession, sOutputAttr, 0, (void**)(void*)&theOutput, &theLen);
  3. if (theLen != sizeof(RTPSessionOutput*))//第一次还未设置<span style="font-family:Arial,Helvetica,sans-serif">sOutputAttr属性,所以theLen为0</span>
  4. {
  5. if (theErr != QTSS_NoErr && !isPush)
  6. {
  7. // 客户端第一次进行DoSetup处理时,进入到这里
  8. // Do the standard ReflectorSession setup, create an RTPSessionOutput
  9. theSession = DoSessionSetup(inParams, qtssRTSPReqFilePathTrunc);
  10. if (theSession == NULL)
  11. return QTSS_RequestFailed;
  12.  
  13. RTPSessionOutput* theNewOutput = NEW RTPSessionOutput(inParams->inClientSession, theSession, sServerPrefs, sStreamCookieAttr );
  14. theSession->AddOutput(theNewOutput,true);
  15. (void)QTSS_SetValue(inParams->inClientSession, sOutputAttr, 0, &theNewOutput, sizeof(theNewOutput));//设置<span style="font-family:Arial,Helvetica,sans-serif">sOutputAttr属性</span>
  16. }
  17. else
  18. { ... 推送端处理 ... }
  19. }

同样,调用DoSessionSetup和FindOrCreateSession,会增加一次对ReflectorSession的引用,+1.

当客户端第二次进入到DoSetup流程时,QTSS_GetValuePtr(inParams->inClientSession, sOutputAttr, 0, (void**)(void*)&theOutput, &theLen)函数已经能够获取到theOutput,直接进入

  1. else
  2. { theSession = (*theOutput)->GetReflectorSession();
  3. if (theSession == NULL)
  4. return QTSS_RequestFailed;
  5. }

这样,并不会增加对ReflectorSession的引用,对后面客户端的第N次DoSetup流程调用,都不会增加对ReflectorSession的引用。

当客户端进入DoPlay流程时,由于在DoPlay之前调用的ProcessRTSPRequest函数中

  1. QTSS_Error ProcessRTSPRequest(QTSS_StandardRTSP_Params* inParams)
  2. {
  3. ...
  4.  
  5. RTPSessionOutput** theOutput = NULL;
  6. QTSS_Error theErr = QTSS_GetValuePtr(inParams->inClientSession, sOutputAttr, 0, (void**)(void*)&theOutput, &theLen);
  7. if ((theErr != QTSS_NoErr) || (theLen != sizeof(RTPSessionOutput*))) // a broadcaster push session
  8. { if (*theMethod == qtssPlayMethod || *theMethod == qtssRecordMethod)
  9. return DoPlay(inParams, NULL);
  10. else
  11. return QTSS_RequestFailed;
  12. }
  13.  
  14. switch (*theMethod)
  15. {
  16. case qtssPlayMethod:
  17. return DoPlay(inParams, (*theOutput)->GetReflectorSession());//此时已经赋值ReflectorSession
  18. ...
  19. }
  20. return QTSS_NoErr;
  21. }

在DoPlay函数时,inSession参数已经赋值,所以后面就没有再从sSessionMap中增加引用。

如此,在客户端流程中,一共对ReflectorSession引用了+2次,一次是在DoDescribe,一次是在DoSetup.

我们再分别看推送端和客户端离线时,对ReflectorSession的处理:

推送端,在我们之前的博客http://blog.csdn.net/xiejiashu/article/details/8065717,已经修改过,也就是,当推送端断开时,会调用到QTSSReflectorModule中的DestroySession函数

  1. QTSS_Error DestroySession(QTSS_ClientSessionClosing_Params* inParams)
  2. {
  3. RTPSessionOutput** theOutput = NULL;
  4. ReflectorOutput* outputPtr = NULL;
  5. ReflectorSession* theSession = NULL;
  6. OSMutexLocker locker (sSessionMap->GetMutex());
  7.  
  8. UInt32 theLen = sizeof(theSession);
  9. QTSS_Error theErr = QTSS_GetValue(inParams->inClientSession, sClientBroadcastSessionAttr, 0, &theSession, &theLen);
  10. if (theSession != NULL) // it is a broadcaster session
  11. {
  12. ReflectorSession* deletedSession = NULL;
  13. theErr = QTSS_SetValue(inParams->inClientSession, sClientBroadcastSessionAttr, 0, &deletedSession, sizeof(deletedSession)); ...
  14. RemoveOutput(NULL, theSession, killClients);
  15. }
  16. else
  17. { ... }
  18. }
  1. void RemoveOutput(ReflectorOutput* inOutput, ReflectorSession* inSession, Bool16 killClients)
  2. {
  3. ...
  4. if (inSession != NULL)
  5. {
  6. ...
  7. OSRef* theSessionRef = inSession->GetRef();
  8. if (theSessionRef != NULL)
  9. {
  10. for (UInt32 x = 0; x < inSession->GetNumStreams(); x++)
  11. {
  12. if (inSession->GetStreamByIndex(x) == NULL)
  13. continue;
  14.  
  15. Assert(theSessionRef->GetRefCount() > 0) //this shouldn't happen.
  16. if (theSessionRef->GetRefCount() > 0)
  17. sSessionMap->Release(theSessionRef); // 一路Track释放一次引用
  18. }
  19.  
  20. if (theSessionRef->GetRefCount() == 0)
  21. {
  22. //当ReflectorSession无引用时,进行Delete
  23. sSessionMap->UnRegister(theSessionRef);
  24. delete inSession;
  25. }
  26.  
  27. }
  28. }
  29. delete inOutput;
  30. }

如果单就推送端而言,上面的方法足够了,因为我们在上面的分析中,得知,DoAnnounce、DoSetup、DoPlay、ProcessRTPData总共对ReflectorSession引用次数就是媒体Track的数目(ReflectorSession->GetNumStreams())。那么如果客户端也同样调用到RemoveOutput中来时,一路客户端是固定的2次引用ReflectorSession,只有当Track数目为2时(常见的音视频同时存在),正好达到收支平衡,如果只有视频进行推送的情况下,一路客户端会造成一次引用的堆积,做过测试的就会发现,每一次播放停止,都会对ReflectorSession的总引用数累积1个。如果有3路或者3路以上Track时,又会造成ReflectorSession被过早收回,当还有客户端在看的情况下,被动的关闭,导致错误的发生,那么我们如何来避免呢?

首先,我们对QTSSReflectorModule的DoDescribe函数进行修改,因为我们发现,当客户端进行DoDescribe创建ReflectorSession时,RTPSession并没有任何属性设置了ReflectorSession的指针,也就是说,客户端如果只获取一下媒体的sdp信息就回去了,ReflectorSession的引用岂不是一直都被白白占用着,如此,我们可以在DoDescribe函数的倒数第二行加入一句

  1. sSessionMap->Release(theSession->GetRef());//减少DoDescribe对ReflectorSession的引用

再来修改RemoveOutput函数

  1. OSRef* theSessionRef = inSession->GetRef();
  2. if (theSessionRef != NULL)
  3. {
  4. if(inOutput != NULL) //如果是客户端的话,减少一次ReflectorSession的引用
  5. {
  6. if (theSessionRef->GetRefCount() > 0)
  7. sSessionMap->Release(theSessionRef);
  8. }
  9. else // 如果是推送端的话,减少Track数目个引用
  10. {
  11. for (UInt32 x = 0; x < inSession->GetNumStreams(); x++)
  12. {
  13. if (inSession->GetStreamByIndex(x) == NULL)
  14. continue;
  15.  
  16. Assert(theSessionRef->GetRefCount() > 0) //this shouldn't happen.
  17. if (theSessionRef->GetRefCount() > 0)
  18. sSessionMap->Release(theSessionRef);
  19. }
  20. }
  21.  
  22. if (theSessionRef->GetRefCount() == 0)
  23. {
  24. sSessionMap->UnRegister(theSessionRef);
  25. delete inSession;
  26. }
  27. }

如此一来,客户端在DoDescribe时不占用ReflectorSession引用数,DoSetup引用数在 RemoveOutput中被释放,推送端释放机制未改变,这样就完美解决了问题。

------------------------------------------------------------
本文转自www.easydarwin.org,更多开源流媒体解决方案,请关注我们的微信:EasyDarwin 

Darwin做直播时对ReflectorSession引用数的控制的更多相关文章

  1. [Z] 计算机类会议期刊根据引用数排名

    一位cornell的教授做的计算机类期刊会议依据Microsoft Research引用数的排名 link:http://www.cs.cornell.edu/andru/csconf.html Th ...

  2. MySQL做练习时总结的一些知识点

    MySQL做练习时总结的一些知识点     0:mysql有三种注释方法 上午插入记录的时候一直没有成功,郁闷不知道为什么.因为是很多条记录一起插入,中间一些不用的数据就用"--" ...

  3. 使用C++做算法时,对内存的管理的办法

    使用C++做算法时,对内存的管理的办法 最近老是在想C++的内存控制机制,查了一些资料所以有点想法,自己记录一下免得以后自己忘了. 1. 需求 在做线性代数的算法时,首要的就实现Matrix这个类.由 ...

  4. 在使用R做数据挖掘时,最常用的数据结构莫过于dataframe了,下面列出几种常见的dataframe的操作方法

    原网址 http://blog.sina.com.cn/s/blog_6bb07f83010152z0.html 在使用R做数据挖掘时,最常用的数据结构莫过于dataframe了,下面列出几种常见的d ...

  5. 此类目的是防治序列化Json字符串时的循环引用问题-------最好解决方案

    http://james.newtonking.com/json/help/index.html using Newtonsoft.Json;using System;using System.Col ...

  6. 做dg时遇到的log_archive_dest、log_archive_dest_1、db_recovery_file_dest之间互相影响

    前提:归档开启.默认不指定归档文件夹. 今晚遇到客户那里设置了闪回区和log_archive_dest.不停库做DG时,无法指定log_archive_dest_n參数,巨坑. .实验了下.结论例如以 ...

  7. 复习做UWP时涉及到的几种加密签名相关

    本人菜鸟一枚,大学里凭兴趣学了一点WP的皮毛,后来又幸运(或者不幸)的进了一家专注于Windows生态的公司做了一段时间的UWP.在博客园写点自己遇到的东西,作为分享,也作为自己的备忘,如果有错误的地 ...

  8. angular中service封装$http做权限时拦截403等状态及获取验证码倒计时、跨域问题解决

    封装$http.做权限时拦截403等状态及获取验证码倒计时: 拦截接口返回状态 var app = angular.module('app'); app.factory('UserIntercepto ...

  9. oracle 在C# 中调用oracle的数据库时,出现引用库和当前客户端不兼容的问题解决方案

    oracle 在C# 中调用oracle的数据库时,出现引用库和当前客户端不兼容的问题解决方案 解决方案 1.直接引用  Oracle.ManagedDataAccess.Client.dll动态库即 ...

随机推荐

  1. 获取应用在AppStore的地址和跳转到AppStore

    获取应用程序在App Store中的链接地址 http://itunes.apple.com/cn/app/id533655318?mt=8 把id改成自己app的id就可以了,或者在iTunes和A ...

  2. AtCoder Beginner Contest 084 D - 2017-like Number【数论/素数/前缀和】

    D - 2017-like Number Time limit : 2sec / Memory limit : 256MB Score : 400 points Problem Statement W ...

  3. facebook architecture 2 【转】

    At the scale that Facebook operates, a lot of traditional approaches to serving web content breaks d ...

  4. Python学习笔记——安装

    最近打算使用下GAE,便准备学习一下python.我对python是一窍不通,因此这里将我的学习历程记录下来,方便后续复习. 安装python: 可以从如下地址:http://www.python.o ...

  5. CSS属性clip

    http://blog.sina.com.cn/s/blog_68a1582d0100kp59.html CSS属性中有个裁剪属性clip,其实我对这个属性一点都不感冒,因为我感觉它好像没啥用处,但是 ...

  6. 在dedecms后台发表文章显示外部连接栏目

    问题描述:客户的网站,有个顶级栏目,下面包含了几个子栏目,这个顶级栏目不想发布什么内容,点击后进入他的某个子栏目就可以了,这时候把这个顶级栏目设置为“外部连接”就可以了 但是设置顶级栏目为外部连接后, ...

  7. Poj2826 An Easy Problem

    呵呵哒.WA了无数次,一开始想的办法最终发现都有缺陷.首先需要知道: 1)线段不相交,一定面积为0 2)有一条线段与X轴平行,面积一定为0 3)线段相交,但是能接水的三角形上面线段把下面的线段完全覆盖 ...

  8. 2017.2.20 activiti实战--第五章--用户与组及部署管理(一)用户与组

    学习资料:<Activiti实战> 第五章 用户与组及部署管理(一)用户与组 内容概览:讲解activiti中内置的一套用户.组的关系,以及如何通过API添加.删除.查询. 5.1 用户与 ...

  9. linux中find的用法

    找所在目录的文件用 find -name “file*” -print  #注意不要用加文件路径,查找文件也好用双双引号括住: 也可以 find ./ -name “file*” -print

  10. 通用礼品卡接口文档(KFC、必胜客、GAP等)

    通用礼品卡接口文档,集于各商家(KFC.必胜客.GAP等)实体卡和会员卡的API虚拟卡,可用于线上/下消费.移动支付. 1.API 1.1商品列表 接口地址:http://v.juhe.cn/gift ...