用Darwin开发RTSP级联server(拉模式转发)(附源代码)

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

在博客 在Darwin进行实时视频转发的两种模式 中,我们描写叙述了流媒体server对源端音视频转发的两种模式。当中一种#拉模式# 转发。在我们通常的项目中常常会用到。比方在传统视频监控行业,IP摄像机部署在监控内网的各个地点。我们须要将他们进行集中式的管理,而且对外公布,这时候我们就须要用到一台流媒体server,可以拉取所需的摄像机的音视频流,并做转化(如RTMP、HTTP等)。作为监控内网与公网的中转,提供转发服务。

#转发模块设计

拉模式转发中,转发server一方面作为RTSPclient的角色,向源端摄像机获取音视频数据。还有一方面作为server的角色,将拉取到的音视频数据。又一次作为数据源,分发给正在请求的client。这样,我们在设计中须要考虑到下面几点:

  • 源端数据流到server的数据流可以复用,也就是一路进多路出。
  • server端维护全部正在分发的摄像机源列表。
  • server与源端在空暇状态下无连接,仅仅有在有须要的情况下才发起连接过程。
  • 当全部client结束对某个源端请求时。server停止从源端获取数据。断开连接。

Darwin系统已经具有了我们所需的一定条件:RTSPClientclient实现、RTP分发流程(ReflectorSession)。我们须要实现:Darwin拉模式转发模块,我们定义此模块名称为QTSSOnDemandRelayModule,意为仅仅有在有须要的时候,才会转发;Darwin与源端用于交互、保存信息、接收数据的ClientSession,为了不影响Darwin原有的架构,我们没有直接在RTSPClient类中改动。而是自己定义类:RTSPClientSession,实例化RTSPClient对象为其成员变量:

在RTSPClientSession中。全部RTSP流程都由fClient(RTSPClient对象)完毕,RTSPClientSession负责进行变量存储(如server地址fAddr、portfPort、usernamefName、passwordfPassword)、收到数据包统计(fStates、fNumPacketReceived)、RTSPClient控制(SETUP发送fNumSetups、RTSP断开fTeardownImmediately)、以及在非client断开情况下。server与摄像机间的重连

#转发模块实现

我们命名拉模式转发模块名称为:QTSSOnDemandRelayModule。须要分别实现对RTSP和RTP的转发和处理。如此,我们会分别处理QTSS_RTSPPreProcessor_Role(RTSP消息处理)、QTSS_RTSPRelayingData_Role(拉取的RTP数据处理)、QTSS_ClientSessionClosing_Role(client或RTSPClientSession断开处理)。

*QTSS_RTSPPreProcessor_Role(RTSP消息处理)

我们设计的拉模式转发为名称与地址映射的方式,映射列表配置在xml文件里。在QTSSOnDemandRelayModule初始化时,我们就会将配置映射表载入到模块中,当然!我们也能够改动为读取数据库的方式:

比如。RTSP摄像机地址为:rtsp://218.204.223.237:554/live/1/66251FC11353191F/e7ooqwcfbqjoo80j.sdp,RTSP转发server地址为:8.8.8.8。port为:554。那么client请求:rtsp://8.8.8.8:554/ipC1,转发server就会向rtsp://218.204.223.237:554/live/1/66251FC11353191F/e7ooqwcfbqjoo80j.sdp
请求摄像机数据,获取后转发给client列表。

映射查找我们在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
	{
		// 初次建立server与摄像机间的交互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(client和RTSPClientSession断开处理)

ReflectorSessionclient引用数统计、client端断开流程、RTSPClientSession断开流程。基本与RTSPSession(client与设备推送)方法一样:

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)
            {
				// 当引用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;
}

#演示效果

#下载

程序下载:http://pan.baidu.com/s/1c09vY6k ,执行start.bat。详细使用方法请看ReadMe.txt

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

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

时间: 2024-10-11 10:19:09

用Darwin开发RTSP级联server(拉模式转发)(附源代码)的相关文章

【COCOS2D-HTML5 开发之三】演示样例项目附源代码及执行的GIF效果图

本站文章均为李华明Himi原创,转载务必在明显处注明:(作者新浪微博:@李华明Himi) 转载自[黑米GameDev街区] 原文链接: http://www.himigame.com/cocos2d-html5/1528.html ? 点击订阅 ? 本博客最新动态!及时将最新博文通知您! Cocos2dx html5开发,对于用过2d Or -x的童鞋来说非常easy,Himi这里也没有必要去再跟同学们具体的教学一遍. 所以Himi简单做了一个项目,供给大家參考,源代码下载地址及GIF截图在文章

推拉模式

上面已提到消费端感知topic服务器有新消息叫推拉模式 左图采用短连接,server端不能获取clinet连接资源,无法主动推给client.所以要求client定时过来拿数据 右图采用长连接,server端可以获取clinet连接资源,有消息过来就能主动推给client 短连接应用受环境因素限制,如b/s架构 应用是寄生在浏览器执行,还没出现websocket之前只能用js ajax技术来解决 还有是移动端,网络经常抖动不稳定,用短连接实施成本上是很低的,但缺点很明显非常占用流量 推拉模式常用

水晶报表的推拉模式的具体实现

水晶报表在应用时分两种方法,分别是拉模式(PULL).推模式(PUSH). 拉模式:在水晶报表生成时的数据源是从水晶报表文件中的SQL语句从数据库中提取的,在编程时不用重写SQL语句,但要加上登录信息. 推模式:在水晶报表生成时的数据源,是用编程时重写水晶报表中SQL语句而生成的dataset对像.也就是说,推模式是用dataset组装水晶报表. 1. 拉模式实现: DATATABLE不用在代码中实现,直接把参数和登陆信息传递给报表即可. public ReportDocument Crysta

JQuery和ASP.NET分别实现级联下拉框效果

在学习JQuery之前知道下拉框的级联效果可以通过asp.net控件实现,现在学习了JQuery,知道了JQuery和select也能实现.我分别举两个小例子说明这两种方法如何实现. 1.用JQuery和select来实现汽车厂商和汽车类型的联动 效果图:       逻辑分析图: html代码: [html] view plain copy <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "

ajax实现级联下拉菜单

ajax实现级联下拉菜单非常简单,下面是一个用ajax实现的省市级联的下拉菜单: jsp中在<select>中添加onchange事件,触发该事件调用实现ajax的js: <div> <form action="Pcs" method="post"> 所在省:<select name="pro" id="pro" onchange="chCity();"> &

微博feed系统的推(push)模式和拉(pull)模式和时间分区拉模式架构探讨

sns系统,微博系统都应用到了feed(每条微博或者sns里的新鲜事等我们称作feed)系统,不管是twitter.com或者国内的新浪微博,人人网等,在各种技术社区,技术大会上都在分享自己的feed架构,也就是推拉模式(timyang上次也分享了新浪微薄的模式).下面我们就微博的feed推拉(push,pull)模式做一下探讨,并提出新的时间分区拉模式. 众所周知,在微博中,当你发表一篇微博,那么所有关注你的followers(粉丝)都会在一定的时间内收到你的微薄,这有点像群发一封邮件,所有的

SQL Server 恢复模式、备份方式和还原方式之间的简单关系

恢复模式 备份方式 还原方式 恢复模式 简单模式 完整备份.差异备份 完整还原.差异还原 恢复模式 完整模式 完整备份.差异备份,事务日志备份 完整还原.差异还原.事务日志还原(可还原到备份的任意时间点) 恢复模式 大容量日志 完整备份.差异备份,事务日志备份 完整还原.差异还原.事务日志还原(不可还原到备份的任意时间点) 附注: 恢复模式:简单模式在完整备份和差异备份的过程对日志进行标记"可复用",即截断了日志: 恢复模式:完整模式在完整备份和差异备份不对日志进行标记,只有事务日志备

NetMQ(ZeroMQ)Client =&gt; Server =&gt; Client 模式的实现

ØMQ (也拼写作ZeroMQ,0MQ或ZMQ)是一个为可伸缩的分布式或并发应用程序设计的高性能异步消息库.它提供一个消息队列, 但是与面向消息的中间件不同,ZeroMQ的运行不需要专门的消息代理(message broker).该库设计成常见的套接字风格的API. ZeroMQ是由iMatix公司和大量贡献者组成的社群共同开发的.ZeroQ通过许多第三方软件支持大部分流行的编程语言 .类库提供一些套接字(对传统Berkeley套接字和Unix domain socket的泛化),每一个套接字可

jquery级联下拉框

$(document).ready(function(){     //找到三个下拉框     var carnameSelect = $(".carname").children("select");     var cartypeSelect = $(".cartype").children("select");     var wheeltypeSelect = $(".wheeltype").chi