原文地址:http://blog.csdn.net/xiejiashu/article/details/29580543
在博客 在Darwin進行實時視頻轉發的兩種模式 中,我們描述了流媒體服務器對源端音視頻轉發的兩種模式,其中一種#拉模式# 轉發,在我們通常的項目中經常會用到,比如在傳統視頻監控行業,IP攝像機部署在監控內網的各個地點,我們需要將他們進行集中式的管理,並且對外發布,這時候我們就需要用到一臺流媒體服務器,能夠拉取所需的攝像機的音視頻流,並做轉化(如RTMP、HTTP等),作爲監控內網與公網的中轉,提供轉發服務。
#轉發模塊設計
拉模式轉發中,轉發服務器一方面作爲RTSP客戶端的角色,向源端攝像機獲取音視頻數據,另一方面作爲服務器的角色,將拉取到的音視頻數據,重新作爲數據源,分發給正在請求的客戶端。這樣,我們在設計中需要考慮到以下幾點:
- 源端數據流到服務器的數據流能夠複用,也就是一路進多路出。
- 服務器端維護所有正在分發的攝像機源列表。
- 服務器與源端在空閒狀態下無連接,只有在有需要的情況下才發起連接過程。
- 當所有客戶端結束對某個源端請求時,服務器停止從源端獲取數據,斷開連接。
- QTSS_Error DoDescribe(QTSS_StandardRTSP_Params* inParams)
- {
- char* theUriStr = NULL;
- QTSS_Error err = QTSS_GetValueAsString(inParams->inRTSPRequest, qtssRTSPReqFileName, 0, &theUriStr);
- Assert(err == QTSS_NoErr);
- if(err != QTSS_NoErr)
- return QTSSModuleUtils::SendErrorResponse(inParams->inRTSPRequest, qtssClientBadRequest, 0);
- QTSSCharArrayDeleter theUriStrDeleter(theUriStr);
- // 查找配置表,獲取攝像機信息結構體
- DeviceInfo* pDeviceInfo = parseDevice->GetDeviceInfoByIdName(theUriStr);
- if(pDeviceInfo == NULL)
- {
- // 映射表中沒有查到相關信息,返回,RTSP請求交給其他模塊處理
- return QTSS_RequestFailed;
- }
- // 映射信息存在rtsp://218.204.223.237:554/live/1/66251FC11353191F/e7ooqwcfbqjoo80j.sdp
- RTSPClientSession* clientSes = NULL;
- // 首先查找RTSPClientSession Hash表是否已經建立了對應攝像機的RTSPClientSession
- StrPtrLen streamName(theUriStr);
- OSRef* clientSesRef = sClientSessionMap->Resolve(&streamName);
- if(clientSesRef != NULL)
- {
- clientSes = (RTSPClientSession*)clientSesRef->GetObject();
- }
- else
- {
- // 初次建立服務器與攝像機間的交互RTSPClientSession
- clientSes = NEW RTSPClientSession(
- SocketUtils::ConvertStringToAddr(pDeviceInfo->m_szIP),
- pDeviceInfo->m_nPort,
- pDeviceInfo->m_szSourceUrl,
- 1,
- rtcpInterval,
- 0,
- theReadInterval,
- sockRcvBuf,
- speed,
- packetPlayHeader,
- overbufferwindowInK,
- sendOptions,
- pDeviceInfo->m_szUser,
- pDeviceInfo->m_szPassword,
- theUriStr);
- // 向攝像機源端發送Describe請求
- OS_Error theErr = clientSes->SendDescribe();
- if(theErr == QTSS_NoErr){
- // 將成功建立的RTSPClientSession註冊到sClientSessionMap表中
- OS_Error theErr = sClientSessionMap->Register(clientSes->GetRef());
- Assert(theErr == QTSS_NoErr);
- }
- else{
- clientSes->Signal(Task::kKillEvent);
- return QTSSModuleUtils::SendErrorResponse(inParams->inRTSPRequest, qtssClientNotFound, 0);
- }
- //增加一次對RTSPClientSession的無效引用,後面會統一釋放
- OSRef* debug = sClientSessionMap->Resolve(&streamName);
- Assert(debug == clientSes->GetRef());
- }
- // 建立轉發所用的ReflectorSession,後續流程與QTSSReflectorModule類似
- ReflectorSession* theSession = SetupProxySession(inParams, clientSes);
- if (theSession == NULL)
- {
- sClientSessionMap->Release(clientSes->GetRef());
- return QTSSModuleUtils::SendErrorResponse(inParams->inRTSPRequest, qtssServerNotImplemented, 0);
- }
- }
這裏我們用到了兩個Hash Map,一個是存儲RTSPClientSession的sClientSessionMap、一個存儲ReflectorSession的sSessionMap。
- void RemoveOutput(ReflectorOutput* inOutput, ReflectorSession* inSession, Bool16 killClients)
- {
- // 從ReflectorSession中移除RTSPSession
- Assert(inSession);
- if (inSession != NULL)
- {
- if (inOutput != NULL)
- inSession->RemoveOutput(inOutput,true);
- OSMutexLocker locker (sSessionMap->GetMutex());
- OSRef* theSessionRef = inSession->GetRef();
- if (theSessionRef != NULL)
- {
- if (theSessionRef->GetRefCount() == 0)
- {
- // 當引用客戶端數量爲0的時候,通知RTSPClientSession斷開與攝像機的連接
- RTSPClientSession* proxySession = (RTSPClientSession*)inSession->GetRelaySession();
- if(proxySession != NULL)
- {
- proxySession->SetReflectorSession(NULL);
- sClientSessionMap->UnRegister(proxySession->GetRef());
- proxySession->Signal(Task::kKillEvent);
- }
- inSession->SetRelaySession(NULL);
- sSessionMap->UnRegister(theSessionRef);
- delete inSession;
- }
- else
- {
- qtss_printf("QTSSReflector.cpp:RemoveOutput Release SESSION=%lu RefCount=%d\n",(UInt32)inSession,theSessionRef->GetRefCount());
- sSessionMap->Release(theSessionRef);
- }
- }
- }
- delete inOutput;
- }