• 基于DSS的先侦听后推送式流媒体转发


          前面文章中说到的,DSS转发可以划分为先拉后推和先侦听后推送两种模式,今天我们解析的是DSS进行的先侦听后推送的流程,具体流程可以大致描述为:源端或者中继端(我们称之为推送端)先通过主动的连接,告知推送端信息(ID,IP等等),服务器维护与源端的会话Session,建立一定的保活与超时机制,并通过此路Session相互交换控制或者上送信息,其中就包含流媒体推送的命令。可按照具体的需求,服务器可通过命令发送的方式,开启源端的推送流程(通过已经建立的Session自定义开启交互的命令),也可以是源端主动开始推送流程(DSS中描述为Broadcast广播),具体的RTSP推送流程大致为:Announce、Setup、Play、RTP(DSS为RTP over TCP)。这种模式的转发通常用于类似于3G视频监控这种难以穿透的网络类型的数据的转发。

          那么我们就不具体介绍关于DSS对会话的维护以及各自自定义的RTSP头字段的操作等等,主要就步骤:Announce->Setup->Play->RTP数据接收与转发进行详细的分析。在DSS中,处理推送报文的模块为QTSSReflectorModule,其中维护了一个静态的转发列表sSessionMap,用于存储各个转发会话的信息。下面就对具体的报文解析和数据处理进行分析。

          Announce:RTSP Announce命令为源端向服务器端主动发起的上报本地媒体sdp信息的命令,处理函数为QTSSReflectorModule模块的DoAnnounce()函数,这里就只对该函数的重点部分进行解析,不全部一一描述了。首先判断server配置中的enable_broadcast_announce字段是否为true,开启了广播推送转发,在通过获取inParams->inRTSPRequest(在RTSPSession::Run调用前复制的当前请求的rtspRequest对象)的字典中的qtssRTSPReqLocalPath键值作为标识转发的唯一区别(例如:.\Movies/test.sdp,必须以sdp结尾,可以修改sSDPSuffix进行配置),这里的值既是一个标识,又是一个路径,用于存储获取到的sdp数据,后面此标识作为存储于sSessionMap中对象的键值。函数中通过对头字段的解析,获取到Content-Length:字段值,进而去读取具体的spd值,再存储到qtssRTSPReqLocalPath路径中,返回200 OK。

          Setup:这里的只解析DoSetup中isPush为true(表示为推送的Session)这条路路径,具体isPush值由Setup请求中的mode值有关,mode="receive" || mode="record"表示isPush为true,

    {           
                theSession = DoSessionSetup(inParams, qtssRTSPReqFilePathTrunc,isPush,&foundSession); //根据前面Announce中存储于qtssRTSPReqLocalPath的路径读取sdp信息,创建转发会话ReflectorSession,或者直接引用已经存在的Session
                if (theSession == NULL)
                    return QTSS_RequestFailed;  
                // This is an incoming data session. Set the Reflector Session in the ClientSession
                theErr = QTSS_SetValue(inParams->inClientSession, sClientBroadcastSessionAttr, 0, &theSession, sizeof(theSession));//ReflectorSession附属于RTPSession中的sClientBroadcastSessionAttr字典
                Assert(theErr == QTSS_NoErr);
                //qtss_printf("QTSSReflectorModule.cpp:SETsession    sClientBroadcastSessionAttr=%lu theSession=%lu err=%ld \n",(UInt32)sClientBroadcastSessionAttr, (UInt32) theSession,theErr);
                (void) QTSS_SetValue(inParams->inClientSession, qtssCliSesTimeoutMsec, 0, &sBroadcasterSessionTimeoutMilliSecs, sizeof(sBroadcasterSessionTimeoutMilliSecs));
           }

    这里需要注意的是,当我们前面已经有一路相同qtssRTSPReqLocalPath路径的ReflectorSession存在的时候,将不进行再创建,直接Resolve原有的ReflectorSession,所以会出现一种情况,当开始的推送与后面进行的推送音视频sdp不一致的时候,就会出现错误,所以, ReflectorSession的引用与释放需要注意!

    完成ReflectorSession的创建,下一步解析track ID,具体的解析方法可以根据自己的实际应用,有的按照track%d解析,有的按照trackID=%d解析,再根据trackId获取具体的track sdp信息,AddRTPStream创建对应于具体track的RTP流

            theStreamInfo->fSetupToReceive = true;//标识流转发的建立
            // This is an incoming data session. Set the Reflector Session in the ClientSession
            theErr = QTSS_SetValue(inParams->inClientSession, sClientBroadcastSessionAttr, 0, &theSession, sizeof(theSession));//设置转发会话的RTPSession字典的sClientBroadcastSessionAttr字段
            Assert(theErr == QTSS_NoErr);
                
            if (theSession != NULL)
                theSession->AddBroadcasterClientSession(inParams);//设置ReflectorSession的fBroadcasterSession属性为inParams->inClientSession,呵呵比较乱噢,相当于相互引用


          Play: 具体到DoPlay过程,isPush为true的路径就比较简单了,只是将推送的RTSPSession中的sRTSPBroadcastSessionAttr属性设置为前面DoSetup中获取到的ReflectorSession

            theLen = sizeof(inSession);
            theErr = QTSS_GetValue(inParams->inClientSession, sClientBroadcastSessionAttr, 0, &inSession, &theLen);//DoSetup()中已经设置sClientBroadcastSessionAttr属性
            if (theErr != QTSS_NoErr)
                return QTSS_RequestFailed;
                
            theErr = QTSS_SetValue(inParams->inClientSession, sKillClientsEnabledAttr, 0, &sTearDownClientsOnDisconnect, sizeof(sTearDownClientsOnDisconnect));
            if (theErr != QTSS_NoErr)
                return QTSS_RequestFailed;
    
            Assert(inSession != NULL);
            
            theErr = QTSS_SetValue(inParams->inRTSPSession, sRTSPBroadcastSessionAttr, 0, &inSession, sizeof(inSession));//设置到inParams->inRTSPSession的sRTSPBroadcastSessionAttr属性
           if (theErr != QTSS_NoErr)
                return QTSS_RequestFailed;


          RTP数据处理:ProcessRTPData(),这里只处理RTP over TCP的数据,根据RTP数据中的channel值,调用特定的ReflectorStream进行处理和转发,具体函数为:ProcessRTPData(),先通过前面在DoSetup() & isPush为true时设置的sRTSPBroadcastSessionAttr属性,获取ReflectorSession

    ReflectorSession* theSession = NULL;
        UInt32 theLen = sizeof(theSession);
        QTSS_Error theErr = QTSS_GetValue(inParams->inRTSPSession, sRTSPBroadcastSessionAttr, 0, &theSession, &theLen);
        if (theSession == NULL || theErr != QTSS_NoErr) 
            return QTSS_NoErr;

    再根据channelID获取具体的ReflectorStream并进行数据推送,给具体的ReflectorStream进行处理

            UInt32 inIndex = packetChannel / 2; // one stream per every 2 channels rtcp channel handled below
            ReflectorStream* theStream = NULL;
            if (inIndex < numStreams) 
            {   theStream = theSession->GetStreamByIndex(inIndex);//获取对应track的ReflectorStream
    
                SourceInfo::StreamInfo* theStreamInfo =theStream->GetStreamInfo();  
                UInt16 serverReceivePort =theStreamInfo->fPort;         
    
                Bool16 isRTCP =false;
                if (theStream != NULL)
                {   if (packetChannel & 1)
                    {   serverReceivePort ++;
                        isRTCP = true;
                    }
                    theStream->PushPacket(rtpPacket,packetDataLen, isRTCP);//推送数据给ReflectorStream并转发给分发列表
                }
            }
    

    好了,今天就到这里,接下来的文章中,我们将讲述客户端如何接入到ReflectorSession中,以及theStream->PushPacket如何将rtpPacket推送至各个接入到的Output中~!

    流媒体开发交流群:288214068
     

  • 相关阅读:
    jQuery 重新温习 遗忘知识点
    正则表达式获取博客园随笔1
    用django创建一个简单的sns
    WCF小实例以及三种宿主
    iOS: imageIO完成渐进加载图片
    Excel 菜单系统
    分布式EventBus的Socket实现
    Jenkins安装plugin
    邮件系统存储设计问答
    在Windows上使用CodeLite+MinGW+Clang进行开发
  • 原文地址:https://www.cnblogs.com/babosa/p/5904729.html
Copyright © 2020-2023  润新知