分类: 其他平台
2017-07-19 12:18:58
Onvif(Open Network Video Interface Forum,开放型网络视频接口论坛),是安迅士联合博世及索尼公司共同成立的一个国际开放型网络视频产品标准网络接口开发论坛,以公开、开放的原则共同制定的开放型行业标准。
Onvif标准网络视频设备之间的信息交换定义通用协议,包括实时视频、音频、元数据和控制信息等。网络视频产品由此所能提供的多种可能性,使终端用户,集成商,顾问和生产厂商能够轻松地从中获益,并获得高性价比、更灵活的解决方案、市场扩张的机会以及更低的风险。
gSOAP一种跨平台的C和 C++软件开发工具包。生成C/C++的RPC代码,XML数据绑定,对SOAP Web服务和其他应用形成高效的具体架构解析器。
WSDL(网络服务描述语言)是一个用来描述Web服务和说明如何与Web服务通信的XML语言。
本软件是用gSOAP实现了onvif中定义的设备发现和Webservice服务。在使用gSOAP时首先是根据wsdl文件生成相应的头文件,然后根据头文件里申明的功能函数,在源文件中实现这些功能函数。
1生成onvif依赖文件
下载gSOAP在上面生成的onvif.h里申明了很多函数,需要在源文件中实现这些函数。
目录及概述
编译脚本
(略......)
第三方依赖库
然后运行onvif程序,便会在终端输出日志
1 live555
1.1 工作模块
UsageEnvironment
该类库是对系统环境的抽象,包括UsageEnvironment 和TaskScheduler。UsageEnvironment
主要用于消息的输入输出和用户功能,TaskScheduler实现事件的异步处理,事件处理函数的注册等。它通过维护一个异步读取源实现如消息到达等事件的处理,通过使用DelayQueue实现其他注册函数的延时调度。另外,还有一个HashTable类定义了一个通用的hash表,其它代码要用
到这个表。我们在使用时可以自定义该类的抽象类的子类,就可以再特定的环境下运行如嵌入式或者GUI等。不需要进行太多的修改。
groupsock
该类是对网络接口的封装,用于收发数据包。groupsock主要是面向多播数据的收发,它也同时支持单播数据的接收。
liveMedia
该类是live555的核心模块,各种媒体的封装和数据的发送。其中基类为Medium,其他的类都派生自该类。如MediaSession,RTP会话类,一个
session又可以包含多个subsession。还有比较重要的两个派生类Source和Sink,Source抽象了需要发送的数据,Sink则抽象数据的发送者,数据的流动可以经过多个source和sink,两者又通过session联系在一起。我们在开发的过程中,可以通过继承这些类,实现自己需要的相关功能。
BasicUsageEnvironment
该类主要针对简单控制台的应用程序,利用select实现事件的获取和处理。
1.2 工作流程
live555首先会创建一个RTSP服务(具体的实现可以参看mediaServer里的服务)。在服务创建过程中,会先调用setUpOurSocket建立tcp的连接,并监听对应传入的port,用于等待client请求的rtsp协议的交互,然后会把连接处理句柄已经socket句柄都传入TaskScheduler当中,等待事件触发。
(流程图)
1、初始化
BasicTaskschedular
BasicUsageEnvironment
RTSPServer
|--------new RTSPServer
|------setupOurSocket创建监听客户端连接用的socket
|------turnOnBackgroundhandling(socket)将监听socket加入计划任务,等待连接
2、接受客户端连接 incomingConnectionHandlerRTSP
在RTSPServer将监听socket加入计划任务后,调度机制会不断查询,如果收到连接请求,就会调用该回掉函数
(Tips:在Live555中监听任务的执行都是通过相应的静态全局回掉函数,在回掉函数内部再强制转换到相应的类函数)
在incomingConnectionHandlerRTSP中会clientSocket = accept()得到连接的socket、地址,设置参数等
之后创建一个客户连接管理createNewClientConnection()
|----new RTSPClientConnection
|---setBackgroundHandling将连接的socket加入计划任务
(Tips:turnonBackgroundHandling只让socket可读,内部会调用setBackgroundHandling进行设置,如需其他属性,如可写、异常等都需要通过setBackgroundHandling设置)
3、响应请求过程
3.1 一般过程
incomingRequestHandler
|---readSocket() 读取数据
|---handleRequestBytes() 解析数据并做相应的处理
|---parseRTSPRequestString() 解析请求
|---handleCmd_XXX() 根据不同的RTSP协议命令去处理
|---send(fResponseBuffer) 构造好回应字段后,发送回应
3.2具体过程
3.2.1 handleCmd_DESCRIBE
handleCmd_DESCRIBE
|---urlTotalSuffix 提取streamName
|---authenticationOK 验证用户,这里只保留了接口,未进行实现
|---fOurServer.lookupServerMediaSession(streamName)
这个用来获取ServerMediaSession,一般不同类型的服务器会有不同的实现策略,如Live555MediaServer只用来流化本地文件,所以有了继承类DynamicRTSPServer,重写lookupServerMediaSession方法,本来找不到session实例会返回NULL,而在这里将查找当前目录下对应的本地文件,然后去创建相应的session,这也就是为什么要将文件与程序放在同一目录下的原因。
这里的ServerMediaSession在创建时会同时添加ServerMediasubsession,因为是服务器,所以一定知道自己应该创建些什么subsession
|---sdpDescription =session->generateSDPDescription() 获取SDP描述信息
|---snprintf() 组建回复字符串
3.2.1.1 generateSDPDescription过程
generateSDPDescription
|---foreach subsession sdpLines =subsession->sdpLines() 获取每个subsessiond的sdp描述
|---sdpLines() 过程
|---onDemandServerMediasubsession 为点播式流媒体服务创建的中间继承类,Live555Mediasever中的subsession都继承自此类
|---FrameSource createNewStreamSource 创建一个数据源的Souce
|---RTPSink createNewRTPSink 创建RTPSink
|---setSDPLinesFromRTPSink(source, sink)从临时的source与sink中获取sdp
(Tips:createNewStreamSource与createNewRTPSink都是抽象方法,需要子类去实现,之后会从setSDPLinesFromRTPSink(source,
sink)中得到sdp信息,从这里也可以看出sdp信息由RTPSink获得。Live555服务器的机制是sink从source中获取数据,创建sink的时候会将source作为参数传入,因为服务器知道创建了什么类型的subSession,所以对一般的媒体信息sink都会具备,唯一需要获取的是getAuxSDPLine(),OnDemandServerMediasubsession默认的处理方式是从RTPSink中的auxSDPLine获取,如果没有就返回NULL,对Live555MediaServer来说,传输H264文件时是无法得到sps、pps信息的,必须通过读取文件,所以在H264subsession中就选择创建Source和Sink读取一段信息后解析获得)
3.2.2 handleCmd_SETUP
handleCmd_SETUP
|---sessionID 找一个唯一的sessionID,用于标识当前的subsession
|---fourServer.createNewClientSession(sessionID)
|---clientSession.handleCmd_SETUP() 转到RTSPClientSession中去处理
|---fOurServerMediaSubsession = sms =fourSrever.lookupServerMediasession
|---创建streamState结构
|---fStreamStates = new struct streamState[fNumStreamStates]
|---foreach subSessionfstreamState[i].subsession = subsession 将subsession装进streamState结构
|---trackID,subsession->trackID 通过trackID寻找对应的subSession
|---parseTransportHeader 解析Transport参数
|---subsession->getStreamParameters 在这里将建立真正的FrameSource与RTPSink
|---FrameSource *mediaSource = createNewStreamSource()
|---rtoGroupsock = new GroupSock(serverRTPPort)
|---rtcpGroupsock = new Groupsock(serverRTPPort)
|---rtpSink = creatNewRTPSink(rtpGroupsock, mediaSource)
|---streamToken = new StreamState(rtpSink, mediaSource, rtpsock,rtcpsock)
|---fDestinationHashTable->add(sessionId, destination)
3.2.3handleCmd_PLAY
handleCmd_PLAY
|---在RTSPClientSession中,处理PLAY、PAUSE、TEARDOWN用同一个函数接口handle_CmdwithinSession,只是在里面又进行了区分
|---找到对应的subsession->startStream()这里将启动流传输数据
|---streamState.startPlaying() 开始传输
|---如果没有fRTCPInstance就创建一个RTCPInstance::createNew(fRTPSink)
|---fRTPgs->addDestination()
|---fRTCPgs->addDestination()
|---fRTPSink->startPlaying()
|---MediaSink::continuePlay() 到这里就是各个子类实现了,纯虚函数
|---MuliFramedRTPSink::continuePlaying()这里针对的是H264文件的解析
|---buildAndSendPacket()
|---准备RTP包头
|---packFrame 打包帧数据
|---分两种情况:一是上一次没打包完,还有数据;二是一个全新的帧
|---对情况二,会调用fSource->getNextFrame()里面加入的回调函数afterGettingFrame,实际是调用sink->afterGettingFrame1
|---最终会依情况打包
|---sendPacketIfNecessary()
在sendPacketIfNecessary中会调用fRTPInterface.sendPacket()发送数据,之后安排一个延时任务回调sendNext函数,在sendNext中又会调用buildAndSendPacket,从而形成一个回路来不断发送数据,知道检测到fNoFrameLeft
1.3 live555调试方法
1)调节socket发送缓存
(略......)
2)调节live555缓存阀值
(略......)
3)更换协议TCPorUDP
(略......)
4)添加打印日志
(略......)
1.4 安卓平台编译
1、编译脚本
(略......)
2、编译指令:
a) ./genMakefiles
b) make
2 流媒体服务器
本流媒体服务器是基于live555进行的二次开发,作用是实现设备端实时音视频数据的RTSP转发。通过封装jni接口启动流媒体服务器,获取音视频实时数据进行编码、解析后,分包由live555发送。
2.1 目录结构
2.2 编译脚本
(略......)
2.3 jni封装
功能:启动RtspServer
Java_com_eques_device_ui_hardware_RTSPJNI_RtspServer(JNIEnv*env,jobject thiz)
参数名 数据类型 描述
功能:取实时视频数据
Java_com_eques_device_ui_hardware_RTSPJNI_ReadVideoData(JNIEnv*env,jobject thiz,jbyteArray DataIn,jint insize)
参数名 数据类型 描述
DataIn jbyteArray H264数据
insize jint 长度
功能:取实时音频数据
Java_com_eques_device_ui_hardware_RTSPJNI_ReadAudioData(JNIEnv*env,jobject thiz,jbyteArray DataIn,jint insize)
参数名 数据类型 描述
DataIn jbyteArray PCM数据
insize jint 长度
2.4 H264实时流传输
LIVE555默认只支持发送音视频文件,而不支持从媒体设备获取的实时码流。这需要修改LIVE555源代码以实现H264码流实时发送功能。
从实现RTSP服务的相关基类派生出H264码流直播的类,重写类的成员方法来实现。。具体实现方法是添加H264LiveVideoServerMediaSubssion:public
H264VideoFileServerMediaSubsession类,并重写createNewStreamSource成员方法。该成员方法的关键段代码段如下:
(略......)
该代码段的主要工作是把ByteStreamFileSource替换为用户自定义的H264FramedLiveSource,用于获取高清摄像头上的实时视频数据。H264FramedLiveSource的成员方法H264FramedLiveSource::doGetNextFrame就实现了从H264编码输出端获取H264格式视频数据并送到H264orH265VideoRTPSink端的过程。该成员方法的关键代码段如下:
(略......)
这样,当服务器端收到客户端PLAY命令时,不断调用H264FramedLiveSource::doGetNextFrame读取H264格式视频数据,封包和发送出去,实现H264码流实时传输功能。
在live555android.cpp主函数中,只需在创建ServerMediaSession时加入H264LiveVideoServerMediaSubssion,并向RTSPServer中注册该ServerMediaSession即可。
2.5 AAC实时流传输
1、AAC编码
jni接口获取的数据为pcm流,需要编码aac流
需要注意的是pcm到aac编码前需要做位的存储转化代码如下:
(略......)
2、重写类的成员方法来实现,具体实现方法是添加AudioLiveVideoServerMediaSubssion:public
AudioVideoFileServerMediaSubsession类,并重写createNewStreamSource成员方法。该成员方法的关键段代码段如下:
(略......)