源代码下载地址:https://github.com/EasyDarwin orwww.easydarwin.org
在博客 在Darwin进行实时视频转发的两种模式 中,我们描写叙述了流媒体server对源端音视频转发的两种模式。当中一种#拉模式# 转发。在我们通常的项目中常常会用到。比方在传统视频监控行业,IP摄像机部署在监控内网的各个地点。我们须要将他们进行集中式的管理,而且对外公布,这时候我们就须要用到一台流媒体server,可以拉取所需的摄像机的音视频流,并做转化(如RTMP、HTTP等)。作为监控内网与公网的中转,提供转发服务。
#转发模块设计
拉模式转发中,转发server一方面作为RTSPclient的角色,向源端摄像机获取音视频数据。还有一方面作为server的角色,将拉取到的音视频数据。又一次作为数据源,分发给正在请求的client。这样,我们在设计中须要考虑到下面几点:
- 源端数据流到server的数据流可以复用,也就是一路进多路出。
- server端维护全部正在分发的摄像机源列表。
- server与源端在空暇状态下无连接,仅仅有在有须要的情况下才发起连接过程。
- 当全部client结束对某个源端请求时。server停止从源端获取数据。断开连接。
映射查找我们在DoDescribe中进行:
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.sdpRTSPClientSession* clientSes = NULL;// 首先查找RTSPClientSession Hash表是否已经建立了相应摄像机的RTSPClientSessionStrPtrLen streamName(theUriStr);OSRef* clientSesRef = sClientSessionMap->Resolve(&streamName);if(clientSesRef != NULL){clientSes = (RTSPClientSession*)clientSesRef->GetObject();}else{// 初次建立server与摄像机间的交互RTSPClientSessionclientSes = 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中移除RTSPSessionAssert(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){ // 当引用client数量为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;
}
------------------------------------------------------------
