• 用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
    ",(UInt32)inSession,theSessionRef->GetRefCount());
                    sSessionMap->Release(theSessionRef);
                }
            }
        }
        delete inOutput;
    }

    #演示效果



    #下载

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


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

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


  • 相关阅读:
    spring-boot启动debug信息中non-fatal error解决
    is present but cannot be translated into a null value due to being declared as a primitive type
    mysql联合索引
    Spring RestTemplate 专题
    Android Studio开发入门-引用jar及so文件
    它们偷偷干了啥?教你监督APP的运行
    解决Android中No resource found that matches android:TextAppearance.Material.Widget.Button.Inverse问题
    No resource found that matches the given name 'android:WindowTitle'
    Android studio:library module依赖篇
    com.android.support:appcompat-v7:21.+
  • 原文地址:https://www.cnblogs.com/yutingliuyl/p/6747682.html
Copyright © 2020-2023  润新知