用Darwin開發RTSP級聯服務器(拉模式轉發)

原文地址:http://blog.csdn.net/xiejiashu/article/details/29580543  

在博客 在Darwin進行實時視頻轉發的兩種模式 中,我們描述了流媒體服務器對源端音視頻轉發的兩種模式,其中一種#拉模式# 轉發,在我們通常的項目中經常會用到,比如在傳統視頻監控行業,IP攝像機部署在監控內網的各個地點,我們需要將他們進行集中式的管理,並且對外發布,這時候我們就需要用到一臺流媒體服務器,能夠拉取所需的攝像機的音視頻流,並做轉化(如RTMP、HTTP等),作爲監控內網與公網的中轉,提供轉發服務。


#轉發模塊設計

      拉模式轉發中,轉發服務器一方面作爲RTSP客戶端的角色,向源端攝像機獲取音視頻數據,另一方面作爲服務器的角色,將拉取到的音視頻數據,重新作爲數據源,分發給正在請求的客戶端。這樣,我們在設計中需要考慮到以下幾點:

  • 源端數據流到服務器的數據流能夠複用,也就是一路進多路出。
  • 服務器端維護所有正在分發的攝像機源列表。
  • 服務器與源端在空閒狀態下無連接,只有在有需要的情況下才發起連接過程。
  • 當所有客戶端結束對某個源端請求時,服務器停止從源端獲取數據,斷開連接。
      Darwin系統已經具有了我們所需的一定條件:RTSPClient客戶端實現、RTP分發流程(ReflectorSession),我們需要實現:Darwin拉模式轉發模塊,我們定義此模塊名稱爲QTSSOnDemandRelayModule,意爲只有在有需要的時候,纔會轉發;Darwin與源端用於交互、保存信息、接收數據的ClientSession,爲了不影響Darwin原有的架構,我們沒有直接在RTSPClient類中修改,而是自定義類:RTSPClientSession,實例化RTSPClient對象爲其成員變量:



      在RTSPClientSession中,所有RTSP流程都由fClient(RTSPClient對象)完成,RTSPClientSession負責進行變量存儲(如服務器地址fAddr、端口fPort、用戶名fName、密碼fPassword)、收到數據包統計(fStates、fNumPacketReceived)、RTSPClient控制(SETUP發送fNumSetups、RTSP斷開fTeardownImmediately)、以及在非客戶端斷開情況下,服務器與攝像機間的重連


#轉發模塊實現

我們命名拉模式轉發模塊名稱爲:QTSSOnDemandRelayModule,需要分別實現對RTSP和RTP的轉發和處理,如此,我們會分別處理QTSS_RTSPPreProcessor_Role(RTSP消息處理)、QTSS_RTSPRelayingData_Role(拉取的RTP數據處理)、QTSS_ClientSessionClosing_Role(客戶端或RTSPClientSession斷開處理)。



*QTSS_RTSPPreProcessor_Role(RTSP消息處理)

我們設計的拉模式轉發爲名稱與地址映射的方式,映射列表配置在xml文件中,在QTSSOnDemandRelayModule初始化時,我們就會將配置映射表加載到模塊中,當然!我們也可以修改爲讀取數據庫的方式:



例如,RTSP攝像機地址爲:rtsp://218.204.223.237:554/live/1/66251FC11353191F/e7ooqwcfbqjoo80j.sdp,RTSP轉發服務器地址爲:8.8.8.8,端口爲:554,那麼客戶端請求:rtsp://8.8.8.8:554/ipC1,轉發服務器就會向rtsp://218.204.223.237:554/live/1/66251FC11353191F/e7ooqwcfbqjoo80j.sdp 請求攝像機數據,獲取後轉發給客戶端列表。映射查找我們在DoDescribe中進行:

  1. QTSS_Error DoDescribe(QTSS_StandardRTSP_Params* inParams)  
  2. {  
  3.     char* theUriStr = NULL;  
  4.     QTSS_Error err = QTSS_GetValueAsString(inParams->inRTSPRequest, qtssRTSPReqFileName, 0, &theUriStr);  
  5.     Assert(err == QTSS_NoErr);  
  6.     if(err != QTSS_NoErr)  
  7.         return QTSSModuleUtils::SendErrorResponse(inParams->inRTSPRequest, qtssClientBadRequest, 0);  
  8.     QTSSCharArrayDeleter theUriStrDeleter(theUriStr);  
  9.   
  10.     // 查找配置表,獲取攝像機信息結構體  
  11.     DeviceInfo* pDeviceInfo = parseDevice->GetDeviceInfoByIdName(theUriStr);  
  12.   
  13.     if(pDeviceInfo == NULL)  
  14.     {  
  15.         // 映射表中沒有查到相關信息,返回,RTSP請求交給其他模塊處理  
  16.         return QTSS_RequestFailed;  
  17.     }  
  18.   
  19.     // 映射信息存在rtsp://218.204.223.237:554/live/1/66251FC11353191F/e7ooqwcfbqjoo80j.sdp  
  20.     RTSPClientSession* clientSes = NULL;  
  21.     // 首先查找RTSPClientSession Hash表是否已經建立了對應攝像機的RTSPClientSession  
  22.     StrPtrLen streamName(theUriStr);  
  23.     OSRef* clientSesRef = sClientSessionMap->Resolve(&streamName);  
  24.     if(clientSesRef != NULL)  
  25.     {  
  26.         clientSes = (RTSPClientSession*)clientSesRef->GetObject();  
  27.     }  
  28.     else  
  29.     {  
  30.         // 初次建立服務器與攝像機間的交互RTSPClientSession  
  31.         clientSes = NEW RTSPClientSession(  
  32.                                     SocketUtils::ConvertStringToAddr(pDeviceInfo->m_szIP),  
  33.                                     pDeviceInfo->m_nPort,  
  34.                                     pDeviceInfo->m_szSourceUrl,  
  35.                                     1,  
  36.                                     rtcpInterval,  
  37.                                     0,  
  38.                                     theReadInterval,  
  39.                                     sockRcvBuf,  
  40.                                     speed,  
  41.                                     packetPlayHeader,  
  42.                                     overbufferwindowInK,  
  43.                                     sendOptions,  
  44.                                     pDeviceInfo->m_szUser,  
  45.                                     pDeviceInfo->m_szPassword,  
  46.                                     theUriStr);  
  47.   
  48.         // 向攝像機源端發送Describe請求  
  49.         OS_Error theErr = clientSes->SendDescribe();  
  50.   
  51.         if(theErr == QTSS_NoErr){  
  52.             // 將成功建立的RTSPClientSession註冊到sClientSessionMap表中  
  53.             OS_Error theErr = sClientSessionMap->Register(clientSes->GetRef());  
  54.             Assert(theErr == QTSS_NoErr);  
  55.         }  
  56.         else{  
  57.             clientSes->Signal(Task::kKillEvent);  
  58.             return QTSSModuleUtils::SendErrorResponse(inParams->inRTSPRequest, qtssClientNotFound, 0);   
  59.         }  
  60.   
  61.         //增加一次對RTSPClientSession的無效引用,後面會統一釋放  
  62.         OSRef* debug = sClientSessionMap->Resolve(&streamName);  
  63.         Assert(debug == clientSes->GetRef());  
  64.     }  
  65.   
  66.     // 建立轉發所用的ReflectorSession,後續流程與QTSSReflectorModule類似  
  67.     ReflectorSession* theSession = SetupProxySession(inParams, clientSes);  
  68.       
  69.     if (theSession == NULL)  
  70.     {  
  71.         sClientSessionMap->Release(clientSes->GetRef());  
  72.         return QTSSModuleUtils::SendErrorResponse(inParams->inRTSPRequest, qtssServerNotImplemented, 0);  
  73.     }  
  74. }  

      這裏我們用到了兩個Hash Map,一個是存儲RTSPClientSession的sClientSessionMap、一個存儲ReflectorSession的sSessionMap。


*QTSS_RTSPRelayingData_Role(拉取的RTP數據處理)

當RTSPClientSession獲取到一個RTP包時,我們就會調用QTSS_RTSPRelayingData_Role,將RTP包Push給ReflectorSession進行分發,分發過程與QTSSReflectorModule處理方式是一樣的,調用方法也同理:



*QTSS_ClientSessionClosing_Role(客戶端和RTSPClientSession斷開處理)

ReflectorSession客戶端引用數統計、客戶端端斷開流程、RTSPClientSession斷開流程,基本與RTSPSession(客戶端與設備推送)方法一樣:
  1. void RemoveOutput(ReflectorOutput* inOutput, ReflectorSession* inSession, Bool16 killClients)  
  2. {  
  3.     // 從ReflectorSession中移除RTSPSession  
  4.     Assert(inSession);  
  5.     if (inSession != NULL)  
  6.     {  
  7.         if (inOutput != NULL)  
  8.             inSession->RemoveOutput(inOutput,true);  
  9.   
  10.         OSMutexLocker locker (sSessionMap->GetMutex());  
  11.           
  12.         OSRef* theSessionRef = inSession->GetRef();  
  13.         if (theSessionRef != NULL)   
  14.         {                 
  15.             if (theSessionRef->GetRefCount() == 0)  
  16.             {   
  17.                 // 當引用客戶端數量爲0的時候,通知RTSPClientSession斷開與攝像機的連接  
  18.                 RTSPClientSession* proxySession = (RTSPClientSession*)inSession->GetRelaySession();  
  19.                 if(proxySession != NULL)  
  20.                 {  
  21.                     proxySession->SetReflectorSession(NULL);  
  22.                     sClientSessionMap->UnRegister(proxySession->GetRef());  
  23.                     proxySession->Signal(Task::kKillEvent);  
  24.                 }  
  25.   
  26.                 inSession->SetRelaySession(NULL);  
  27.                 sSessionMap->UnRegister(theSessionRef);  
  28.                 delete inSession;  
  29.             }  
  30.             else  
  31.             {  
  32.                 qtss_printf("QTSSReflector.cpp:RemoveOutput Release SESSION=%lu RefCount=%d\n",(UInt32)inSession,theSessionRef->GetRefCount());  
  33.                 sSessionMap->Release(theSessionRef);  
  34.             }  
  35.         }  
  36.     }  
  37.     delete inOutput;  
  38. }  

#演示效果



#下載

程序下載:http://pan.baidu.com/s/1c09vY6k ,運行start.bat,具體用法請看ReadMe.txt !
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章