源码下载地址:https://github.com/EasyDarwin orwww.easydarwin.org

在博客 在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中进行:

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。



*QTSS_RTSPRelayingData_Role(拉取的RTP数据处理)

当RTSPClientSession获取到一个RTP包时,我们就会调用QTSS_RTSPRelayingData_Role,将RTP包Push给ReflectorSession进行分发,分发过程与QTSSReflectorModule处理方式是一样的,调用方法也同理:



*QTSS_ClientSessionClosing_Role(客户端和RTSPClientSession断开处理)

ReflectorSession客户端引用数统计、客户端端断开流程、RTSPClientSession断开流程,基本与RTSPSession(客户端与设备推送)方法一样:
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;
}

#演示效果



#下载

程序下载:http://pan.baidu.com/s/1c09vY6k ,运行start.bat,具体用法请看ReadMe.txt

------------------------------------------------------------

本文转自www.easydarwin.org,更多开源流媒体解决方案,请关注我们的微信:EasyDarwin 


最新文章

  1. [CentOs7]搭建ftp服务器(3)——上传,下载,删除,重命名,新建文件夹
  2. ASP.NET MVC入门之再不学习就真的out了
  3. 一行代码解决各种IE兼容问题IE8,IE9,IE10
  4. 浏览器 - Firefox开发者附加组件
  5. DevExpress GridControl使用(转)
  6. UVALive 7457 Discrete Logarithm Problem (暴力枚举)
  7. Apache Ant在Windows下配置环境变量
  8. make 要点简记
  9. IOS隐藏navigationItem左右按钮的方法
  10. 两个java项目,跨域访问时,浏览器不能正确解析数据问题
  11. DOS命令(一)
  12. linux安装tomcat部署web项目
  13. 关于文档模式、DCOTYPE声明及严格模式
  14. 力扣(LeetCode)258. 各位相加
  15. ASP.NET自定义错误页并返回正确的500、404状态码
  16. Android Fragment(一)
  17. 【Oracle】Oracle 的过程化SQL(PLSQL)中NULL值的处理
  18. tomcat8.0 基本参数调优配置
  19. python 文件操作,os.path.walk()的回调函数打印文件名
  20. Java checked 异常 和 RuntimeException(运行时异常)

热门文章

  1. ibatis 字段类型为int时如何避免默认值得干扰
  2. vue2 如何通过router传递参数
  3. MySQL完全备份脚本:数据+二进制日志+备份日志
  4. LeetCode OJ——Two Sum
  5. 转:c++ Oracle OCCI 编程
  6. 图片点击放大并可点击旋转插件(1)-jquery.artZoom.js
  7. 微信小程序 wx.navigateTo()传参及多个参数方法
  8. Windows Phone 8 与 windows 8 开发技术概览
  9. IDEA连接linux服务器
  10. 洛谷—— P1407 工资