源码下载地址:https://github.com/EasyDarwin orwww.easydarwin.org
在博客 在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;
}
------------------------------------------------------------
