用Darwin实现流媒体转发程序
DSS源码中已经实现了由源端主动推送视频,再将视频转发到客户端的功能,在基于DSS的先侦听后推送式流媒体转发:http://blog.youkuaiyun.com/xiejiashu/article/details/8298583中已经描述,代码可以在http://download.youkuaiyun.com/detail/xiejiashu/5007454中下载到。
那么今天我们描述一下,如何实现拉模式的转发,即转发服务器接受客户端的请求,参数携带转发的url,url可以是以查询字符串的参数形式传输过来,类似于:url=rtsp://192.168.10.8/test.mp4,转发服务器再根据转发的url,经过标准的RTSP Describe、Setup、Play、RTP流程,将视频获取到,转发给客户端列表(支持分发)。
例如,在客户端中输入:rtsp://192.168.1.10/relay.sdp?url=rtsp://114.112.51.234/abc.mp4, 即可实现对rtsp://114.112.51.234/abc.mp4的转发,其中192.168.1.10为转发服务器(也可以说是代理服务器)的地址。
设计思路:
当我们在收到一路RTSP连接请求时,在DSS中为RTSPSession类对象,首先需要解析请求头部是否为转发请求,这里我们区分转发请求与普通点播请求的方式为relay.sdp,以relay.sdp为请求媒体的名称的话,我们就判定此路客户端会话为转发请求,进而解析其请求的后续部分,进行查询字符串的解析,得到需要转发的具体url,建立一路面向url源的会话,我们称之为RTSPRelaySession,RTSPRelaySession以普通RTSP客户端的角色,连接到远程源端,并通过Describe命令,获取到sdp信息进行保存,再转发到请求Describe的客户端,而且Setup、Play分别将对应的响应码返回给客户端,在转发具体的数据时,建立一路ReflectorSession,将获取到的rtp数据转发到添加进ReflectorSession转发列表的客户端中去。
注:我们的转发服务器所能转发的源端,是按照DSS做为数据源写的,所以其区分的track ID是按照trackID=x这种形式来做的,而不是类似于live555的track1/track2这种形式
- {
-
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, sClientBroadcastSessionA ttr, 0, &theSession, sizeof(theSession));//ReflectorSession附属于RTPSession中的sClientBroadcastSessionA ttr字典 -
Assert(theErr == QTSS_NoErr); -
//qtss_printf("QTSSReflectorModule.cpp:SETsession sClientBroadcastSessionA ttr=%lu theSession=%lu err=%ld \n",(UInt32)sClientBroadcastSessionA ttr, (UInt32) theSession,theErr); -
(void) QTSS_SetValue(inParams->inClientSession, qtssCliSesTimeoutMsec, 0, &sBroadcasterSessionTimeo utMilliSecs, sizeof(sBroadcasterSessionTimeo utMilliSecs)); -
}
这里需要注意的是,当我们前面已经有一路相同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, sClientBroadcastSessionA ttr, 0, &theSession, sizeof(theSession));//设置转发会话的RTPSession字典的sClientBroadcastSessionA ttr字段 - Assert(theErr
== QTSS_NoErr); -
- if
(theSession != NULL) -
theSession->AddBroadcasterClientSess ion(inParams);//设置ReflectorSession的fBroadcasterSession属性为inParams->inClientSession,呵呵比较乱噢,相当于相互引用
- theLen
= sizeof(inSession); - theErr
= QTSS_GetValue(inParams->inClientSession, sClientBroadcastSessionA ttr, 0, &inSession, &theLen);//DoSetup()中已经设置sClientBroadcastSessionA ttr属性 - if
(theErr != QTSS_NoErr) -
return QTSS_RequestFailed; -
- theErr
= QTSS_SetValue(inParams->inClientSession, sKillClientsEnabledAttr, 0, &sTearDownClientsOnDiscon nect, sizeof(sTearDownClientsOnDiscon nect)); - if
(theErr != QTSS_NoErr) -
return QTSS_RequestFailed; -
- Assert(inSession
!= NULL); -
- theErr
= QTSS_SetValue(inParams->inRTSPSession, sRTSPBroadcastSessionAtt r, 0, &inSession, sizeof(inSession));//设置到inParams->inRTSPSession的sRTSPBroadcastSessionAtt r属性 - f
(theErr != QTSS_NoErr) -
return QTSS_RequestFailed;
- ReflectorSession*
theSession = NULL; -
UInt32 theLen = sizeof(theSession); -
QTSS_Error theErr = QTSS_GetValue(inParams->inRTSPSession, sRTSPBroadcastSessionAtt r, 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
我们在开发视频直播或者监控类项目的时候,如场馆监控、学校监控、车载监控等等,往往首先希望的是形成一个项目的雏形,然后再在这个框架的基础上进行不断的完善和扩展工作,那么我们今天要给大家介绍的就是,如何形成一个这样的框架:
采集本地音视频数据(Win) -->视频分发服务器-->客户端随意接入
大致流程:我们首先通过DShow采集Windows(XP、Win7经过测试)主机的视频设备(Camera)和音频设备(Mic)的音视频资源,经过live555的RTSP推送流程和RTP封包过程,将直播数据推送给Darwin分发服务器,Darwin分发服务器接收live555的推送数据并做相应的缓存(超过缓存时间的数据将会被移除),同时响应客户端的接入,将推送来的直播数据转发给客户端。
设备采集端:采集端是以live555为基础框架,以DarwinInjector做为与Darwin交互的接口,再在live555 Source端分别实现CamH264VideoStreamFramer
当然,在采集和发送数据之前,我们需要进行的是与Darwin服务器之间的信令通道交互,这里我们采用的是RTSP标准的Announce(SDP payload)、Setup、Play过程,让Darwin服务器端建立转发类(主要是在QTSSReflectorModule中),等待RTP数据的推送。
设备采集端代码:http://pan.baidu.com/share/link?shareid=2213287549&uk=3693441883
转发服务器:转发服务器是RTSP服务器Darwin Streaming Server,在之前的博客:基于DSS的推送式转发已经描述了RTSP标准推送流程,这里只说明一点小小的修改,就是在Darwin中,当设备端意外断线,也就是不经过标准的RTSP Teardown流程就与服务器断开的话,服务器端将会等待设备推送超时才会释放转发资源,那么对于这一点,我们做了一个小小的更改,这个在:Darwin推送端断开的处理问题中已经描述了。那么最新代码的下载可以到EasyDarwin开源项目中去获取:https://github.com/xiejiashu/EasyDarwin,后续对Darwin的扩展,如分布式RTSP服务器、RTSP直播服务器、服务器端录像、Hint模块等功能,都会在这个开源项目中加入,希望有兴趣的朋友加入到这个开源项目组中来。
客户端端:客户端当然不用太多描述,标准的RTSP流播放器都能播放Darwin视频,不过如果有想开发RTSP客户端的朋友,也可以加入到讨论群中(288214068)。
框架扩展:在上述的流程中,我们是在采集程序一开启时,就开始向Darwin服务器推送数据,那么在通常情况下,点播或者直播通常都是On Demand的形式,就是说,只有有需要的时候,我们才推送视频给服务器,没有需要的时候,设备处在空闲状态的,那么我们可不可以在上述的框架上做到这一点呢?当然可以!
如上图,我们可以先在设备端,开启一条tcp通道,连接到Darwin服务器,Darwin服务器中扩展一个管理模块,专门管理与设备的通信与接入,这样,在服务器不需要设备数据的时候,只与设备保持信令通道的连接和畅通就可以了,当有需要设备当前音视频数据的时候,就通过这条信令通道发送开启命令,采集设备收到命令(自定义命令),就开始本地采集、编码、打包和RTP传输流程,RTP通道可以与信令通道公用也可以单独建立,不过建议还是单独建立的好。当服务器端对此设备分发的客户端数量为0或者其他更高级的需求来到时,可以通过信令通道,发送停止命令到采集设备,这样采集设备就停止本地采集、编码等工作,进入最开始时候的空闲等待状态。如此,就实现了实时视频的点播效果。当然,图中的转发模块与管理模块可以在同一个程序里面开发,也可以分开,分为管理服务器和转发服务器。
值得分享!