Darwin做直播时对ReflectorSession引用数的控制

来源:互联网 发布:python复制文件夹 编辑:程序博客网 时间:2024/06/05 01:51

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

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

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

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

[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. if (theSessionRef == NULL)  
  2. {  
  3.    ... //创建ReflectorSession  
  4. }  


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

[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. if (!isPush)  
  2. {   OSRef* debug = sSessionMap->Resolve(inPath);  
  3.     Assert(debug == theSession->GetRef());  
  4. }  
所以,对ReflectorSession的引用在执行第一次DoSetup推送的时候为 +0.

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

[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  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,所以调用到过程

[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  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流程时

[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  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,直接进入

[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. else  
  2. {   theSession = (*theOutput)->GetReflectorSession();  
  3.     if (theSession == NULL)  
  4.         return QTSS_RequestFailed;    
  5. }  
这样,并不会增加对ReflectorSession的引用,对后面客户端的第N次DoSetup流程调用,都不会增加对ReflectorSession的引用。


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

[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  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函数

[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  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. }  


[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  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函数的倒数第二行加入一句

[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. sSessionMap->Release(theSession->GetRef());//减少DoDescribe对ReflectorSession的引用  


再来修改RemoveOutput函数

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

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