/*

*本文基于LIVE555的嵌入式的RTSP流媒体服务器一个设计文档,个中细节现剖于此,有需者可参考指正,同时也方便后期自己查阅。(本版本是基于2011年的live555)

作者:llf_17@qq.com

*/

RTSP SERVER(基于live555)详细设计

这个server的最终情况如下:

性能:D1数据时:

1.8路全开udp

PID USER     PR  NI  VIRT RES  SHR S %CPU %MEM    TIME+ COMMAND

1175root      20   0 65712 23m 3112 R 29.0 34.5 285:13.05 dvrapp_SN6108

2.8路全开tcp

PID USER     PR  NI  VIRT RES  SHR S %CPU %MEM    TIME+ COMMAND

1175root      20   0 65612 23m 3112 R 31.0 34.4 289:03.86 dvrapp_SN6108

文件:

静态库大小:Live555.a  1,585KB

文件个数:  150左右


目录

1.代码移植...3

1.1代码获取...3

1.2文件初步裁剪...3

1.3修改Makefile.3

1.4live555生成的静态库链接到我司的可执行程序中...3

2.功能添加...4

2.1 数据的输入...4

2.2实时视频(H264)的输入和输出...5

2.3实时音频(G711.a)的输入和输出...6

2.4 H264视频离散nal单元输入的实现...6

2.5单播的实现...7

2.6多路连接的实现...7

2.7最大连接数的限制...9

2.8服务端主动断开连接的实现...9

2.9 sdp信息的添加修改...10

2.10重定向的实现...10

3. 代码裁剪...11

4. 效率优化...12

4.1Ring buffer双队列的修改...12

4.2内存拷贝的去除及socketwritev 的实现...13

5. 部分函数说明:...16

6. 部分实验结果...17


1.代码移植

live555代码移植到我司嵌入式平台上。

1.1代码获取

http://www.live555.com/liveMedia/public/

本次移植使用的版本是2011.12.23.

1.2文件初步裁剪

Live555为跨平台库,本移移植旨在arm linux上运行,所以需先裁剪掉其它无关文件。

a.删除之前先生成用于linuxmakefile.进入live文件夹,运行./genMakefiles linux.此时生成了用于linuxMakefile.

b.删除冗余文件和文件夹。

每个文件夹下只保留*.cpp,*.hh, *.c, *.h, Makefile.其余全部删除。

删除文件夹:WindowsAudioInputDevicemediaServer

1.3修改Makefile

Makefile变量主要做以下修改:

C_COMPILER =              arm-hismall-linux-gcc

CPLUSPLUS_COMPILER =    arm-hismall-linux-g++

LINK =                    arm-hismall-linux-g++-o

LINK_OPTS =          -L.-lpthread

C_FLAGS =              $(COMPILE_OPTS)     $(CFLAGSARM)

CPLUSPLUS_FLAGS =    $(COMPILE_OPTS)$(CFLAGSARM)

1.4live555生成的静态库链接到我司的可执行程序中

如:dvrapp_sn6108

裁剪到此时的Live555编译时会生成所有的库,不可能将所有库链进可执行程序中。我司只用到了视频:H264,音频 G711.a.其余不用。故只需将这几个有关库链接进即可。

我司链接静态库到可执行程序的做法是,先成一个xx.a,然后在生成可执行程序时,链接所有.a文件。所以我们这里只需将需要的.o文件装进live555.a即可。

具体做法:在live555文件夹下新建文件夹liveLib用于存放其它文件生成的.a. 此文件夹用于生成live555.a最终库。(将所有.a先打散成.o,再合成一个live555.a.每个文件夹下在生成.a时都拷贝一份.aliveLib中。最后将生成的live555.a拷贝到master\LIB\ARM\SN6108中。可执行程序链接时在此文件夹下可找到live555.a

2.功能添加

Live555源码的功能要用到我司具体项目中还需做一定的修改,不是拿来就能用的。

原本对于开源项目,尤其是c++项目,最好不要改动原有的类,所以修改应该是继承父类,在子类中修改。这样有利于代码的升级,和维护。但是由于考虑到c++的继承的层数以及虚函数的运行时绑定对性能的影响,以及编译出文件的大小,所以本修改中只对一部分类作了继承,另一些直接在原有类中添加新方法。

2.1 数据的输入

数据从ringbufferserver模块的传输,使用了通知机制,即当ringbuf有数据时,数据发出通道,告知某一通道有数据可用,则server在需要的时候会来这个通道来取数据。

spacer.gif     数据输入框图

通知机制主要由以下函数实现。

void signalNewFrameData(int mediaType, int chanel,int trans_mode,int buf_len)

signalNewFrameData为一个全局函数,可被外部线程调用(注意,整个live555是一个单线程程序)。signalnewFrameData会调用virtual void triggerEvent(EventTriggerId eventTriggerId, void*clientData = NULL), 这个函数共两个参数,一个是触发的事件id,另一个是此id对应的事件处理函数所在的类实例指针,这里具体是各个输入的videoSourceaudioSource类实例指针。

数据的写入ringbuffer由以下函数实现:

write_unicast_data_live(只用于单播),每当一个数据到来后,先判断是音频还是视频,然后再装入各自对应的ringbuffer,接着调用 signalnewFrameData通知相应的server. 通知时刻落在server刚好需要数据的时刻区间间的概率较小,大部分情况是(经实验证明了的)server正在处理其它数据或已经取完数据,正在等下次取数据时刻的到来(此时可能正停留在Eventloop 里的sigleStepselect)。所以通知后都会把事件记入一个bitmask类型的变量fTriggersAwaitingHandling(最多可累计挂入32个待处理事件)然后在select结束后,再处理每个TriggerNum所对应的事件(调用Source中的deliverFrame将数据向后传送)。处理完一个事件,则将fTriggersAwaitingHandling中对应的bitmask位清0,singleStep每一次循环中TriggerEvent只处理一个事件(如有未处理完事件,等下一循环再处理)

2.2实时视频(H264)的输入和输出

Live555提供的示例里面有直接读文件的类和使用方法,但没有实时输入的类及其实现。

Live555中数据流基本路线是:

SourceàFilter1àFilter2…àSink

Filter可能有多个,也可能一个也没有。对于h264filter有两个,对于音频g711.a,实现中则没有Filter.

本设计具体实现视频实时输入方法如下:

1.视频输入

live555中,输入为Source类,输出为 Sink类。中间处理环节类称为Filter.

SNDeviceSource类继承于FramedSource,用于实时输入h264视频。该类的实现参考了DeviceSource

SNDeviceSource::deliverFrame()中实现数据的输入。Memmove 将数据拷贝到fTo.(最后优化后已经改为传指针了,没有了内存拷贝).

2 .Filter

对于H264视频Source不是直接到Sink, 而是经过如下:

SNDeviceSourceàH264VideoStreamDiscreteFrameràH264FUAFragmenteràH264VideoRTPSink

中间两个类称为Filter,是对视频数据的进一步处理。

H264VideoStreamDiscreteFramer继承于H264VideoStreamFramer, H264VideoStreamFramer主要是提取sps, ppsH264VideoStreamDiscreteFramer 主要是用于输入 离散的NAL单元,H264FUAFragmenter主要是对H264nal进行分片打包,根据rfc3984FU-A规则进行分片打包。

3 .视频输出

H264VideoRTPSink为原有类实现了h264 rtp包的输出.

视频的打包和发送操作都在H264VideoRTPSink的父类multiFramedRTPSink.其打包发送的流程如下:

sendNextàbuildandSendPacketàpackFrameàgetnextFrameààafterGettingFrameàsendPacketIfnessaryàscheduleDelayedTask àsendNextà。。。

buildandSendPacket会将rtp包头打好(timestamps and sequence num先预留,等取到帧数据后再填充)。

packFrame即将帧数据往rtp包头后面挂。所以其任务就是要取得帧数据,所以调用getnextFrame来从上一游来获取帧数据。对于h264来说,它的上一级由2.12可看出是H264FUAFragmenter, 这个类会将帧数据分好片(<1448字节), 交给multiFramedRTP Sink。好,获取到帧数据后,就是afterGettingFrame了,这里面主要做一些检查工作(检查数据是否正确,buffer是否溢出等各种检查)和补充工作(填充前面所说的timestampsrtp sequence number)。之后便是sendPakcetIfnessary, 这里面会使用tcpudp将数据发送出去。发送完后,需将下一次任务准备一下,即调用scheduleDelayedTasksendNext放入延迟队列中,等待其在singleStep中被调用,再开始下一次发包过程。

提示下,tiemstampssequence num是在doSpecialFrameHandling中做的,这是个虚函数,其具体实现在H264VideoRTPSink中实现。

上面说到: H264FUAFragmenter, 这个类会将帧数据分好片(<1448字节), 交给multiFramedRTP Sink。但是它的数据是从哪来的。其所套路一样,它也是通过getNextFrame从它的上游获H264VideoStreamDiscreteFramer取的,而H264VideoStreamDiscreteFramer又是通过getNextFrame从它的上游SNDeviceSource来获取。形式有些多余,效率上会打折,但有很明显的组件思想,这种设计,典型 c++思想,有利于扩展,像堆积木一样,可堆出更多的功能。当然对于我们只使用其中h264和音频功能来说,这样设计框架有些多余。

2.3实时音频(G711.a)的输入和输出

1、输入

SNAudioDeviceSource继承于DeviceSource.用于音频输入。类似于视频输入,也是能过DeliverFrame()将数据向后传输的。

2、输出

SNG711RTPSink继承于SimpleRTPSink,用于音频rtp包的输出。

simpleRTPSink只用于简单的发包处理,不进行分片,聚合等操作,而我司g711.a音频包大小为320bytes,符合简单打包的条件。

音频流程中没有Filter,直接是:

SNAudioDeviceSourceàSimpleRTPSinkàMultiFramedRTPSink

和视频一样的取数据发送方法,只是环节更少,G711.a音频不需要分片,以及PPS,SPS等提取。MultiFramedRTPSink中的getNextFrame直接从SNAudioDeviceSource中取得帧数据。

2.4 H264视频离散nal单元输入的实现

Live555H264数据的输入有两种方式,一种所谓ByteStream,读文件时采用的主是这种方式,每次读入的不是整数个帧,这些数据还要被解析成独立的帧数据才能进一步操作,这种方法使用的filterH264VideoStreamFramer,它利用H264VideoStreamParser :: parse()从读入的数据中解析出一个个nal单元 。另一种是所谓DiscreteFramer,即输入是一帧帧离散的 nal单元,这就省去了再解析的环节。

对于本公司的数据格式来说,是介于这两种之间。因为对于p帧本司是一个个离散的nal单元,但对于I帧来说,则是四个nal单元(pps,sps,sei,I)拼起来作为一个整体进行输入的。这种不“纯粹性”导致只能把每个输入都当成byteStream,显然对于p帧来说,多余了。

所以,设计将I帧解析成单独的NAL单元后再进行输入,这样所有的输入都是离散的NAL单元,即可以使用DiscreteFramer方式进行输入了。

spacer.gif

设计SNGetNextNalU函数用来实现对帧数据的解析。如果不是视频I帧,则直接通过deliverframe将数据向后传,如果是I帧,则要先从中解析出单独的NAL单元,再通过deliverFrame向后传。

2.5单播的实现

Live555具有单播和多播功能,将单播具体应用到我司环境中,如下:

需继承OnDemandServerMediaSubsession类,重写与自己特定source相关的OnDemandServerMediaSubsession子类。本设计中

class SNG711OnDemandServerMediaSubsession:public OnDemandServerMediaSubsession

classSNH264OnDemandServerMediaSubsession:public OnDemandServerMediaSubsession

其中G711*类用于输入音频子会话,H264对应视频子会话。

其中最主要的是需重写createNewStreamSource createNewRTPSink这两个虚函数,使*subsessionsourcesink关联起来。Soure类和Sink类的实例化就是在这两个函数调用时创建的。

SNH264OnDemandServerMediaSubsession可参考H264VideoFileOnDemandServer MediaSubsession来实现。

SNG711OnDemandServerMediaSubsession有些特别。参考实现文件是WAVAudioFile ServerMediaSubsession.其中音频的位宽(816)和编码方式(a lawu law)都会影响到具体实现方法。所以这些参数一定要提前确定好。Ps:之前由于参数认识错误,导致改了很久还是错的。

2.6多路连接的实现

从单路到多路的实现,主要是将一个输入流输出流改为多个。

spacer.gif多路输入输出图

SNonDemandRTSPServer(相当于本线程的main函数)中,用数组来实现多个ServerMediaSessin,再向每个servermdiaSesson添加各自的subsession (视频:SNH264OnDemandServerMediaSubsession,音频:SNG711OnDemandServerMediaSub session),每个subsession 会管理属于自己的sourcesink

另外,还有一个需注意的是输入的事件通知也要改成多个通道的通知。

多路连接的示意图如下:

多路输入事件的触发有两种方式:

方法1:

使用多个triggerEventId来代表每一路的音/视频输入,本设计使用了这种方法。

但这种法有局限性。即通道数的限制。Live555使用一个32位的bitMask来代表每一个EventTriggerId, 也就是说最多可使用32 tirggerId, 本设计为8通道,每路音视频各需一个triggerId,所以共要8*2=16id. 如果通道数超过16路,按这种方法则有问题。当然也可以将bitMask改成64位的,应该可以解决此问题。

方法2:官方推荐

只用一个eventTriggerId,所有通道数据到达事件用各个voidSNDeviceSource::deliverFrame0(void* clientData)clientData来区分。

这种该去方法在应用中出现问题:数据输入缓慢。初步分析是因为使用了虚函数,而像数据输入这种很频繁的操作,如果每次都要通过查虚函数表来确定具体运行哪个函数的话,就会耗费不少时间,导致输入缓慢,ringbuffer总是满。当然也可能是我方法不对头,后续开发者应该可能会想到其它更好的实现方法。

spacer.gif

多路连接类关系示意图

通道数由宏CHANNEL_NUM_LIVE来设置。

2.7最大连接数的限制

本设计为8通道rtspserver,为避免server上连接上超过八路的客户端,对平均每路性能造成影响,需对最大连接数做限制。

先要知道一个server拥有的client在代码中对应的是RTSPClientSession类。每一连接对应一个rtspClientSession实例。而这个实例由RTSPServer::createNewClientSession函数产生。所以应当限制作其产生的实例个数不超过8个。

设计:在rtspServer类中添加私有成员变量fNumConnectedClients。在每次调用RTSPServer::createNewClientSessionfNumConnectedClients值做相应的增加。当计数达到8时,在createNewClientSession中直接反回,而不产生rtspClientSession实例。

最大连接数可通过SN_CLIENT_LIMIT来设置。

2.8服务端主动断开连接的实现

Server端断开某一通道连接的正常过程一般是:

Client请求断开(teardownàServer响应请求,关闭相应资源à断开连接

半闭资源时,最重要的是关闭顺序要对,而且要关闭完全,否则会造成崩溃或内存泄露。如果要找到每一个资源的关闭地方,自己再写一个函数内,按正确的时间和顺序去关闭难度较大,容易出错。

一个简单的办法是,只能能从服务器端触发clienttearDown请求,则剩下的关闭流程会自动走完,这样做也较安全,本设计用的就是此法。服务器里的rtcp “BYE” packet可以实现此功能。流程是:

Server发送rtcp”BYE”àClientteardownàServer关闭相应资源à断开连接

void DSND_DisConnect(intchn)实现了这个功能。

2.9 sdp信息的添加修改

Sdpsession descript的缩写,会话描述息。是流媒体在会话开始时交互信息的一种方式。一般流媒体使用的都是sdp格式来描述。

Live555sdp信息由char* ServerMediaSession::generateSDPDescription()生成。此函数先生成session级别的sdp信息,如“v=…,o=…,s=…”,然后再补充subsession级别的sdp信息,如“a=fmtp:。。。,a=rtpmap:。。。”.subsession级别的sdp信息填充是在各个rtpSink中实现的。如H264char* H264VideoRTPSink::rtpmapLine(),char const*H264VideoRTPSink::auxSDPLine().

需注意的是subSession级别SDP信息,一般是由rtp/avptype来确定的。

0=<type<96时,一个号码确定唯一的编码及rtpmap参数,也就是说这种情况下,参数是死的,只能按国际标准来,自己不能随便改(当然你非要改成自己想像的那种形式或参数,也可以,只是别人识别不了你时,就麻烦了)。

type>=96时,属于动态范围,号码和参数内容允许不同的公司有不同的参数。H264一般取这个范围的起始数字96,当然你要改为97也没人拦你。

对于我们的音频编码类型G711.apcma类型,其typertp/avp 里规定为8.所以它的采样率,编码率,你不能随便改。

如果非要添加音频sdp非标准化信息到sdp中(命令如山倒,做吧,不管合不合理),可在voidOnDemandServerMediaSubsession::setSDPLinesFromRTPSink中先进行音频/视频的判断,当判断出音频时,填充auxSDPLine字符串,内容便是你想要添加的sdp内容。

关于在OnDemandServerMediaSubsession中进行音视频的判断方法,可通过在Source中设计一个虚函数实现,在运行时,再根据绑定的实例判断出是音频Source 还是视频Source,。本设计中使用sourceType()来实现。

RTP/AVP types可参考下面网址:

http://en.wikipedia.org/wiki/RTP_audio_video_profile#RTP.2FAVP_audio_and_video_payload_types

2.10重定向的实现

为了减轻服务器的压力,有时需将数据重定向到其它服务器,如IPC

重定向功能是rtsp 协议里的内容,但在live555里并没有实现,所以只能自己实现。

实现函数void getOneRedirectUrl(char*dst,const int chid,const int streamType)来对某一通道的请求进行URL重定向,即在RTSP 信令走到DISCRIBE命令时,服务器返回一个重定向后的url, 并结束此次会话。客户端只需连接到新的url即可。

voidRTSPServer::RTSPClientSession::handleRequestBytes(int newBytesRead) 中当收到DESCRIBE请求时,先返回一个错误码

RTSP/1.0 305 UseProxy

再调用getOneRedirectUrl来获取一个指定的url, 在发送了返回码之后,再发送此url.

客户端需在此后发送TEARDWON来结束此次会话。

3. 代码裁剪

1、媒体转发功能下的裁剪:

裁剪后为150多个文件。

Live555代码原有360个以上的文件,里面有各种我们用不到的媒体类型的流媒体实现,如aac,mpeg1,mepg2,mp3,mkv等,我们只需要其中的H264 和有关简单音频的实现方法。所以需对源码进行裁剪。

首先删除明显用不到的媒体类型文件。

liveMedia文件夹:

删除以下列字母开头的文件:AAC, AC3, ADT, AMR, DVGSM, H261, H263, JPEG, Matroska, MP3 ,MPEG1or2, MPEG2,MPEG4ES,QuickTime, Sip, T140text, vob, Vorbis, VP8, WAV, 以及各种文件各中带有*file*的文件(因为我们这是实时流媒体,用不着file)。

然后在Boolean MediaSubsession::createSourceObjects(intuseSpecialRTPoffset) 中做如下修改:这个函数中包含了各种媒体类型,我们将除H264pcma类外的代码内容都删除,否则会编译不过。

Source文件夹(testProgs改名):

保留:SNOnDemandRTSPServer用于serverplayCommontestRTSPClient(这两个用于client), Makefile。除此外,其它全部删除。

其它文件夹下不作修改。

再修改MakefileMakefile中将对应已经删除的媒体对应的语句删除,同时将每个makfile中的文件依赖删除。如:

Media.$(CPP):              include/Media.hh

include/Media.hh:   include/liveMedia_version.hh删除掉也不影响。

2、仅保留重定向功能的裁剪

相对于具有媒体转发功能下的裁剪进一步做了以下裁剪:

删除所有媒体相关的文件。

liveMedia文件夹:删除以下文件

MPEGVideoStreamFramer.$(OBJ)MPEG4VideoStreamFramer.$(OBJ) MPEG4VideoStreamDiscreteFramer.$(OBJ)H264VideoStreamFramer.$(OBJ) H264VideoStreamDiscreteFramer.$(OBJ)MPEGVideoStreamParser.$(OBJ)

H264VideoRTPSink.$(OBJ)

FramedFilter.$(OBJ)  BasicUDPSource.$(OBJ)    $(MPEG_SOURCE_OBJS) SNDeviceSource.$(OBJ)SNAudioDeviceSource.$(OBJ)

BasicUDPSink.$(OBJ)  $(H264_SINK_OBJS)  SimpleRTPSink.$(OBJ) SNG711RTPSink.$(OBJ)

MultiFramedRTPSink.$(OBJ)  VideoRTPSink.$(OBJ)

OnDemandServerMediaSubsession.$(OBJ)SNH264OnDemandServerMediaSubsession.$(OBJ)SNG711OnDemandServerMediaSubsession.$(OBJ)

BitVector.$(OBJ) StreamParser.$(OBJ)

其它文件夹保持不变。

4. 效率优化

刚添加完功能时一路跑起来的cpu利用率为5~6%

优化完之后的CPU利用率,一路为4%左右。8D1全开时为:

//最终性能 8路全开udp

PIDUSER      PR  NI VIRT  RES  SHR S %CPU %MEM    TIME+ COMMAND

1175root      20   0 65712 23m 3112 R 29.0 34.5 285:13.05 dvrapp_SN6108

//最终性能 8路全开tcp

PIDUSER      PR  NI VIRT  RES  SHR S %CPU %MEM    TIME+ COMMAND

1175root      20   0 65612 23m 3112 R 31.0 34.4 289:03.86 dvrapp_SN6108

主要做了以下两点的优化:

4.1Ring buffer双队列的修改

先了解下背景:

基于live555的数据输入模型是模式,即server需要发送什么数据时,server再去索取,对于server(假定他是一个人),他关注的是sendPacket,sendnextPacket, sendnextnext Packet…., 其它所有的过程如取数据,打RTP包等都是sendPacket的辅助过程。所以数据的应该是以输出为主导而不是输入。

对于Ringbuf而言,当server 输入(对于server而言,ringbufserver外的模块)主导模式时,我Rinbuf可以来一个帧,我要求你server发一个帧。

server是输出主导时,我不管你ringbuf有没有来帧,当我server需发送数据时,再向你rinbuf要,你ringbuf给我塞,我只会暂时记下,但并不取数据,一直等到需要时再取。

我们的rinbuffer 是单队列模式,即音频视频混合在一个队列中。

个人分析(可能不对,后来者可做参考),当单队列rinbufferlive555配合时,存在这种情况:server需要视频数据时,去队列里取时,而队列尾部恰好是个音频数据,这会导致此番数据取不到,而取数据之前,此rtp包的包头已经打好,就只等帧数据到来后就可发送。但因没有取到,等吧。。。等到延迟队列触发了下一次sendNextPacket后,RTP包头会重新打,因为这次要发的是另外一帧数据,所以上次只打了包头而没有发送的数据便被覆盖了,没有发。。丢包。。。费时。.反之亦然。

改:改成音视频分开的双队列,保证每次去取时,都进入各自的媒体队列,只有队列里有数据,就一定会取到,而不会出现要音频确碰见视频帧,要视频确碰到音频的情况。

先说一下改动后的结果:本来是冲着减小cpu占用率去的,但没什么效果。然而性能上有一个改观就是:基本上不会发生队列满的情况。队列不满,在发送前便不会丢包(试验结果:八路全开,udp, 跑了两天,没有出现过一次ringbuf满的情况)。

单队列改双队列的基本队列的存取逻辑还和单队列一样,只是来一个数据时,先判音视频,再分别送入各自的队列。双队列rinbuffer如下图所示:

spacer.gif

主要的逻辑不同处在于,队列满时,该怎么判断,怎么处理。因为有2个队列,便 有以下4种情况:

a .Video full, audio not full

b. video full, audio full

c. video not full, audio full

d. video not full, audio not full

首先确立video主导地位,即videofull,情况a,b ,清空所有队列,不管audio 是否full.

情况d为正常情况。

情况c , audio不做清空,video不做清空。为添加新结点,Audio队列需删除最后(最old)一个结点,再添加新结点。

情况以c发生的概率较小,因为音频很小,很快就发送完毕业,而大概率的事件应该是视频满,因为视频节点数据大,发送比较费时。(然而这2种满的情况都还没有碰到过).

4.2内存拷贝的去除及socket writev 的实现

为了减小cpu利用率,内存拷贝,首当其冲被想到要被去掉。

Live555中至少有一次内存拷贝,对于H264来说有两次,对于g711.a来说有一次。

一次内存都不要则要用socketwirtev函数来将分散的各个内存块发送出去。

首先定义3个结构体:

typedef struct _ptr{

unsigned char* nalBufStart;

unsigned char* qBufDataPtr;

int theLastNalu;//1(is the last).or 0(notthe last) , becase SN capasulate 4 nal in one Frame called I framed, free mustafter all 4 nal have passed

}BufPtr;

typedef struct _VecPtr{

unsigned char* firstPtr;

unsigned char* secndPtr;

unsigned char* qBufDataPtr;

int firstSize;//will be 2 or 0

int secndSize;

int theLastNaluOrFrame;

int theLastPacketOfOneFrame;

}VecPtr;

typedef struct{

unsigned char* bufStart;

unsigned curOffset;

unsigned TotSize;

unsigned type;

}BufferType;

BufPtr用于从Source::DeliverFrame中进行指针的赋值.

VecPtr fFrameData定义在multFramedRTPSink中,用于存放wirtev所需的参数指针。

BufferType用于h264视频分片打包时的数据指针存储。

1、视频流程的修改

H264视频传输经过的类如下:

SNDeviceSourceàH264videoDiscreteFrameràH264FUAFragmentàH264VideoRTPSinkà MuitiFramedRTPSink

要把数据流经路线上的所有类中有关数据的操作都操作掉,利用传指针。

BufferTypefNalu;

BufPtr fBufptr;

以上两个变量定义在H264FuaFragment中,fBufptr从前方 Source中获取数据指针(指向ringbuffer中的数据)。fNalu用于记录nal单元在分片打包时,各分片的指针状态。

各个类中的修改如下:

SNDeviceSource::

修改deliverFrame, 去除内存拷贝,给传入的指针赋值,此时的fTo即传入的结构体指针fBufptr

H264videoDiscreteFramer::

修改afterGettingFrame1,需将sps, pps 拷贝下来,以备后用,这个类中的指针也要做相应的修改。

H264FUAFragment::

1.afterGettingFrame1fNalu进行赋值。

2.修改doGetnextFrame, 将各个分片内存拷贝取消。并将指针传向后面流程(multframedRTPSink

3.重写构造函数,对FinputBufSize做修改,改成2bytes, 只用来存放分片头和分片指示单元。关于这个可参考rfc3984 fua 分片打包规则。

MultiFramedRTPSink::

1,afterGettingFrame1中对fVecBuf[3~5]进行填充。fVecbuf[3]指向RTP包头,fVecbuf[4]指向分片头或分片对+分片指示单元,fVecbuf[5]指向除分片和指示单元外的帧数据部分。

fVecbuf[0~2], 用来存入TCP方式发送时的三块头字节。

关于wirtev这里说一下,fVecbuf[i]可能NULL,这时,writev会动跳过 ,接着发fVecbuf[i+1], 因此设6vecbuf, 并不意味着6块内存一定要都有数据。

2.void MultiFramedRTPSink::sendPacketIfNecessary()

修改fRTPInterface.sendPacket

RTPInterface::

1.重载BooleanRTPInterface::sendPacket(struct iovec *vecBuf,int vecCnt,unsigned packetSize)

2. 添加函数Boolean sendRTPOverTCP2(structiovec *vecBuf, int vecCnt,unsigned packetSize, int socketNum, unsigned charstreamChannelId)

此时实现了视频的TCP 方式下的writev操作。

经测试,性能未见明显提升,悲剧。。。

2、音频流程的修改

音频相对较简单。其数据流程如下:

SNAudioDeviceSourceà SNG711RTPSinkàMuitiFramedRTPSink

SNAudioDeviceSource:

修改deliverFrame, 对传入VecPtrfTo)赋值。

MuitiFramedRTPSink

afterGettingFrame1中对fVecBuf[3~5]进行填充。

3 UDPsendtotcp witev混合实现。

因为udp 使用writev时,会先进行connect,而《unix网络编程》中关于udp 使用connect方法指出,它只能与一个对端进行数据报的交互,所以当UDP使用了WIRTEV后,对于某一个特定的通道,它只能有一个客户端,当有多个客户端去连接此通道时,只有最后一个连接能成功,前面的即使刚开始成功,也会被后连接的客户端给挤掉。

权衡考虑,决定tcp writev, udp仍用sendto.

其实这个有个简单的方法就是将writev参数里的各个vecBuf[i] 里的数据再拷贝到一整块内存中,然后再sendto发送。本设计即用这种方法实现。也就是说,UDP最终还是用了一次内存拷贝。

UDP改动中,需做以下修改:

1.添加 BooleanwriteSocket2(UsageEnvironment& env,

int socket, struct in_addr address, Portport,

u_int8_t ttlArg,

struct iovec *vecBuf, int vecCnt,unsignedbufferSize)

2.重载Boolean OutputSocket::write

3.重载Boolean Groupsock::output

4 FREE_BUF的调用

当指针指向的数据使用完之后应调用FREE_BUF来减小数据节点的引用计数。

两个判断是否该FREE_BUF

a.是否是最后一个RTP包发掉了(一个视频帧节点可分多个RTP包来发送)。用FrameData.theLastPacketOfOneFrame==1判断。

b.是否是最后节点的最后一部分。对于视频P帧和音频帧,一帧永远是节点的最后一部分,因为节点只有这一个帧。而对于我司H264视频来说,key frame节点里面包含了sps,pps,sei,I 4NAL单元,所以对于这个节点,一帧发完了仍然不能free这个节点,而要到这4个帧都发完了才能free此节点。用fFrameData.theLastNaluOrFrame== 1判断。


5.部分函数说明:

void write_unicast_data_live(int chid,  unsigned char *data, int datalen, unsignedchar *header, int StreamIdx)

功能:

将数据写入ringbuffer. 只用于单播放.

参数:

Chid:数据来源通道

Data:QBUFFER_DATA类型的数据指针

Datalen:数据的长度

Header:ringbuffer结点头,用来保存该节点的某些信息,如:video or audio,I or p frame

StreamIdx:没有用到

返回值:无

void signalNewFrameData(int mediaType, int chanel, inttrans_mode,int buf_len)

功能:

通知某一通道数据可用。

参数:

mediaType:媒体类型,VIDEO_LIVE  AUDIO_LIVE

chanel: 通道号 1~8

trans_mode: 单播或多播。UNICAT_LIVEMULTICAST_LIVE

buf_len: 当前ringbuf的长度,这个参数用处不大。

返回值:无

SNDeviceSource::SNGetNextNalU(unsigned&lenNal,unsigned char &type)

功能:

查找出一个NALu, 从我司的I frame中。需配合IFrameStatus结构体使用。

参数:

lenNal: 用来保存找到的NAL单元的长度,包括startcode

type: 查找到的NALU的类型

返回值:

0  已经找到下一个startCode 00000001

1  没有找到下一个startCode(这是这一块中的最后一个nal单元)

-1 错误,解析对象不正确,非我司I.

void DSND_DisConnect(int chn)

功能:

断开某一通道(1~max)的连接.包播放已经建立在此通道上的所有连接(一个通道连接了八路客户端也是有可能的)。

参数:

Chn,通道号,url中指定的那个通道号

返回值:

.

注意:

一个通道对应一个ServermediaSession实例。

一个连接对应一个RTSPClientSession实例。

一个session包含有2subsession,一个音频,一个视频。在关闭时要都关闭掉。

int FramedSource::sourceType()父类

int SNDeviceSource::sourceType()视频子类

int SNAudioDeviceSource::sourceType()音频子类

功能:

返回此实例的种类。用于父子类,兄弟类之间的身份确认。

返回值:在SNDeviceSource中返回VIDEO_LIVE

SNAudioDeviceSource中返回AUDIO_LIVE

FramedSource中返回0

注意:运行时任何一个FramedSource类型的对象调用sourceType便可知道它是哪个实例。

void getOneRedirectUrl(char *dst,const int chid,const intstreamType)

功能:

获取一个重定向URL, dst指针指向新的url.

参数:

dst:目标URL指针

chid:通道号 1~8

streamType:MAINSTREAM SUBSTREAM

返回值:无

6. 部分实验结果

cpu and memory rate:

测试环境:SN6000 D1

以下为top �p pid的显示结果

//两次内存拷贝省去, TCP writev

tcp writev 1channel  connected

1167 root     20   0 65552  23m 3092 R 5.7 34.3   0:32.48 dvrapp_SN6108

tcp writev 4channel  connected

PID USER     PR  NI  VIRT RES  SHR S %CPU %MEM    TIME+ COMMAND

1167 root     20   0 66944  24m 3096 R 22.3 36.3   4:49.36 dvrapp_SN6108

//两次内存拷贝省去, UDP writev(只支持单客户端连接)

PID USER     PR  NI  VIRT RES  SHR S %CPU %MEM    TIME+ COMMAND

1167 root     20   0 65272  22m 3128 R 4.3 33.9   1:22.50 dvrapp_SN6108

//udp sendto,iovec数组再合成一个完整的内存区域供udp sendto使用

PID USER     PR  NI  VIRT RES  SHR S %CPU %MEM    TIME+ COMMAND

1167 root     20   0 64900  22m 3124 R 4.3 33.4   0:13.06dvrapp_SN6108

//媒体走通后,信号量阻塞方式。

Tcp writev ,1channle connected

PID USER      PR NI  VIRT  RES SHR S %CPU %MEM    TIME+  COMMAND

1193 root     20   0 65008  22m 3092 S 5.0 33.5   0:08.13dvrapp_SN6108

//改双队列后1通道UDP

PID USER     PR  NI  VIRT RES  SHR S %CPU %MEM    TIME+ COMMAND

1175 root     20   0 65900  23m 3120 R 4.3 34.8   0:03.82 dvrapp_SN6108

//改双队列后8通道全开UDP

PID USER     PR  NI  VIRT RES  SHR S %CPU %MEM    TIME+ COMMAND

1201 root     20   0 64576  22m 3144 R 29.7 32.9   0:48.49 dvrapp_SN6108

//改双队列后8通道全开TCP

PID USER     PR  NI  VIRT RES  SHR S %CPU %MEM    TIME+ COMMAND

1175 root     20   0 66252  23m 3120 R 32.3 35.3   0:53.46 dvrapp_SN6108

//改双队列后1通道TCP

PID USER     PR  NI  VIRT RES  SHR S %CPU %MEM    TIME+ COMMAND

1175 root     20   0 64084  21m 3120 R 4.0 32.2   1:14.59dvrapp_SN6108

//4UDP,去掉SNservermediaSession  SNh264videortpsink snrtspserversnrtspclientsession

PID USER     PR  NI  VIRT RES  SHR S %CPU %MEM    TIME+ COMMAND

1175 root     20   0 63464  21m 3092 R 13.7 31.3   0:30.24 dvrapp_SN6108

//最终性能8路全开udp

PID USER     PR  NI  VIRT RES  SHR S %CPU %MEM    TIME+ COMMAND

1175 root     20   0 65712  23m 3112 R 29.0 34.5 285:13.05dvrapp_SN6108

//最终性能8路全开tcp

PID USER     PR  NI  VIRT RES  SHR S %CPU %MEM    TIME+ COMMAND

1175 root     20   0 65612  23m 3112 R 31.0 34.4 289:03.86 dvrapp_SN6108

【视频开发】RTSP SERVER(基于live555)详细设计相关推荐

  1. [live555]rtsp直播基于live555的实现

    一直很想做流媒体的直播,最近花时间看了有关live555的有关代码,这里隆重的推荐两篇: http://blog.csdn.net/nkmnkm (道长的文章,分析的很不错) http://blog. ...

  2. JavaCV音视频开发宝典:基于JavaCV实现wav音频直播服务,wav在线FM电台直播服务,无需流媒体服务,浏览器原生audio标签直接播放wav直播音频

    <JavaCV音视频开发宝典>专栏目录导航 <JavaCV音视频开发宝典>专栏介绍和目录 ​ 前言 之前写过了mp3实现FM电台直播服务:<JavaCV音视频开发宝典:J ...

  3. 基于live555的视频直播 DM368IPNC RTSP分析

    因需要,从个人的理解顺序和需求角度对live555的分析与开发整理,包含RTSP Server与RTSP Client.如何直播H.264流与JPEG流等,均进行了探讨,对live555的初学者有一定 ...

  4. 基于live555的视频直播

    基于live555的视频直播 一直很想做流媒体的直播,最近花时间看了有关live555的有关代码,这里隆重的推荐两篇: http://blog.csdn.net/nkmnkm(道长的文章,分析的很不错 ...

  5. 做音视频开发,你读对书了吗?(内有福利)

    相比于易学难精的很多技术,音视频开发可以说是难入门.更难精通了,尤其是那些查无可查的bug想想都头痛,就算是从业十多年的老兵或许都还有很多棘手的案例,"音视频开发--从入门到弃坑" ...

  6. 《音视频开发进阶指南》读书笔记(一) —— 音视频基础概念

    前言 最近要学音视频,在图书馆借到这本<音视频开发进阶指南>,读了一段时间觉得挺好就在某宝买了. 以后一段时间应该都会沉浸在研究音视频中,开个专题记录哈每一章的读书笔记吧(以iOS开发的角 ...

  7. 视频监控、直播——基于opencv,libx264,live555的RTSP流媒体服务器 (zc301P摄像头)By Chain_Gank...

    一个月一步步的学习历程已经在我前面的随笔中.现在终于迎来了最后一步 不多说,贴代码,不懂的,先看看我之前的随笔,有一步步的过程.还是不懂就在评论中问. #ifndef _DYNAMIC_RTSP_SE ...

  8. 基于live555中的liveMedia库的client开发流程 此博文包含图片

    基于live555中的liveMedia库的client开发流程 转载▼ 如今流媒体无处不在,而主流流媒体服务器为Realworks.Windows Media Server.Apple Darwin ...

  9. live555的安装 RTSP点播消息流程实例(客户端:VLC, RTSP服务器:LIVE555 Media Server)

    live555是一个开源的软件,主要用来生成rtsp,rtp和sip服务器和客户端的软件.前几天需要看一下vlc中的rtsp的功能,在vlc中 rtp和rtsp的功能都是使用live555中的函数来生 ...

最新文章

  1. Cell Research丨潘涛/骆观正合作团队揭示肠道菌群调控宿主RNA甲基化和基因表达新机制...
  2. 深入了解 TabNet :架构详解和分类代码实现
  3. 【回顾】紫丁香一队的故事
  4. 变长参数模板 和 外部模板
  5. redis 失效时间单位是秒还是毫秒_【redis中键的生存时间(expire) 】
  6. 自然语言处理 —— 2.1 词汇表征
  7. linux 进程占用cpu查看工具,Linux下如何查看某一进程的CPU占用率
  8. bzoj 3357: [Usaco2004]等差数列(DP+map)
  9. argparse模块
  10. java7 3dm下载_我的世界 1.7.10最新forge极简整合包
  11. python处理excel数值为文本_使用Python中的xlrd将数字Excel数据读取为文本
  12. Unity Fleck Map 参数说明
  13. workbench设置单元坐标系_ansys workbench中新建坐标系的问题,求高人指点。
  14. 测量的基准面和基准线
  15. 计算机基础知识-进制的运算
  16. pr2020lut导入_pr lut预设怎么安装-PR下导入lut预设的方法 - 河东软件园
  17. 计算机组成原理画出CPU与主存及3-8译码器之间信号线的连接问题
  18. chatgpt的一些思考
  19. java+分割+汉字和英文_Java分割中英文,并且中文不能分割一半?
  20. 美语音标、节奏与语调以及发音技巧

热门文章

  1. 【时间序列分析】17.Hilbert空间的投影与Wold表示定理
  2. linux内核内存申请函数:devm_kzalloc函数的实现进行解析
  3. html5计算文件hash,spark-md5生成hash码,spark-md5计算大文件hash码实现断点续传
  4. 星淘惠:亚马逊仍是跨境电商主流深化选品才能做到事半功
  5. “gjw” is of a model that is not supported by this version of Xcode. Please use a different device.
  6. 【考研】东北大学二叉树相关算法(2)
  7. php威客程序(仿猪八戒),[转载]网上卖300元的PHP威客程序(仿猪八戒)功能强大
  8. 废话中的极品废话 单反对焦系统全解析
  9. 写个小文件让自己的电脑定时关机吧!
  10. Hyperledger Fabric 2.0 官方文档中文版 第6章 教程(下)