Andy Niu Help
1.0.0.0
|
视频音频
详细描述
变量说明
H264_NAUL头的解析 |
1、NAL全称Network Abstract Layer,即网络抽象层。 在H.264/AVC视频编码标准中,整个系统框架被分为了两个层面:视频编码层面(VCL)和网络抽象层面(NAL)。 其中,前者负责有效表示视频数据的内容,而后者则负责格式化数据并提供头信息,以保证数据适合各种信道和存储介质上的传输。 NAL单元是NAL的基本语法结构,它包含一个字节的头信息和一系列来自VCL的称为原始字节序列载荷(RBSP)的字节流。 2、如果NALU对应的Slice为一帧的开始,则用4字节表示,即0x00000001;否则用3字节表示,0x000001。 NAL Header:forbidden_bit,nal_reference_bit(优先级)2bit,nal_unit_type(类型)5bit。 3、标识NAL单元中的RBSP数据类型,其中,nal_unit_type为1, 2, 3, 4, 5的NAL单元称为VCL的NAL单元, 其他类型的NAL单元为非VCL的NAL单元,如下: 0:未规定 1:非IDR图像中不采用数据划分的片段 2:非IDR图像中A类数据划分片段 3:非IDR图像中B类数据划分片段 4:非IDR图像中C类数据划分片段 5:IDR图像的片段 6:补充增强信息(SEI) 7:序列参数集(SPS) 8:图像参数集(PPS) 9:分割符 10:序列结束符 11:流结束符 12:填充数据 13:序列参数集扩展 14:带前缀的NAL单元 15:子序列参数集 16 – 18:保留 19:不采用数据划分的辅助编码图像片段 20:编码片段扩展 21 – 23:保留 24 – 31:未规定
H264码流数据帧 |
1、H264是新一代的编码标准,以高压缩高质量和支持多种网络的流媒体传输著称,在编码方面,我理解的理论依据是: 参照一段时间内图像的统计结果表明,在相邻几幅图像画面中,一般有差别的像素只有10%以内的点,亮度差值变化不超过2%, 而色度差值的变化只有1%以内。所以对于一段变化不大图像画面,我们可以先编码出一个完整的图像帧A, 随后的B帧就不编码全部图像,只写入与A帧的差别,这样B帧的大小就只有完整帧的1/10或更小!B帧之后的C帧如果变化不大, 我们可以继续以参考B的方式编码C帧,这样循环下去。这段图像我们称为一个序列(序列就是有相同特点的一段数据), 当某个图像与之前的图像变化很大,无法参考前面的帧来生成,那我们就结束上一个序列,开始下一段序列, 也就是对这个图像生成一个完整帧A1,随后的图像就参考A1生成,只写入与A1的差别内容。 2、在H264协议里定义了三种帧,完整编码的帧叫I帧,参考之前的I帧生成的只包含差异部分编码的帧叫P帧, 还有一种参考前后的帧编码的帧叫B帧。H264采用的核心算法是帧内压缩和帧间压缩,帧内压缩是生成I帧的算法, 帧间压缩是生成B帧和P帧的算法。 3、序列的说明 在H264中图像以序列为单位进行组织,一个序列是一段图像编码后的数据流,以I帧开始,到下一个I帧结束。 一个序列的第一个图像叫做 IDR 图像(立即刷新图像),IDR 图像都是 I 帧图像。H.264 引入 IDR 图像是为了解码的重同步, 当解码器解码到 IDR 图像时,立即将参考帧队列清空,将已解码的数据全部输出或抛弃,重新查找参数集,开始一个新的序列。 这样,如果前一个序列出现重大错误,在这里可以获得重新同步的机会。IDR图像之后的图像永远不会使用IDR之前的图像的数据来解码。 一个序列就是一段内容差异不太大的图像编码后生成的一串数据流。当运动变化比较少时, 一个序列可以很长,因为运动变化少就代表图像画面的内容变动很小,所以就可以编一个I帧,然后一直P帧、B帧了。 当运动变化多时,可能一个序列就比较短了,比如就包含一个I帧和3、4个P帧。 4、三种帧的说明如下: 5、I帧:帧内编码帧,I帧表示关键帧,你可以理解为这一帧画面的完整保留;解码时只需要本帧数据就可以完成(因为包含完整画面) I帧特点: 1.它是一个全帧压缩编码帧。它将全帧图像信息进行JPEG压缩编码及传输; 2.解码时仅用I帧的数据就可重构完整图像; 3.I帧描述了图像背景和运动主体的详情; 4.I帧不需要参考其他画面而生成; 5.I帧是P帧和B帧的参考帧(其质量直接影响到同组中以后各帧的质量); 6.I帧是帧组GOP的基础帧(第一帧),在一组中只有一个I帧; 7.I帧不需要考虑运动矢量; 8.I帧所占数据的信息量比较大。 6、P帧:前向预测编码帧。P帧表示的是这一帧跟之前的一个关键帧(或P帧)的差别, 解码时需要用之前缓存的画面叠加上本帧定义的差别,生成最终画面。 (也就是差别帧,P帧没有完整画面数据,只有与前一帧的画面差别的数据) P帧的预测与重构:P帧是以I帧为参考帧,在I帧中找出P帧“某点”的预测值和运动矢量,取预测差值和运动矢量一起传送。 在接收端根据运动矢量从I帧中找出P帧“某点”的预测值并与差值相加以得到P帧“某点”样值,从而可得到完整的P帧。 P帧特点: 1.P帧是I帧后面相隔1~2帧的编码帧; 2.P帧采用运动补偿的方法传送它与前面的I或P帧的差值及运动矢量(预测误差); 3.解码时必须将I帧中的预测值与预测误差求和后才能重构完整的P帧图像; 4.P帧属于前向预测的帧间编码。它只参考前面最靠近它的I帧或P帧; 5.P帧可以是其后面P帧的参考帧,也可以是其前后的B帧的参考帧; 6.由于P帧是参考帧,它可能造成解码错误的扩散; 7.由于是差值传送,P帧的压缩比较高。 7、B帧:双向预测内插编码帧。B帧是双向差别帧,也就是B帧记录的是本帧与前后帧的差别 (具体比较复杂,有4种情况,但我这样说简单些),换言之,要解码B帧,不仅要取得之前的缓存画面,还要解码之后的画面, 通过前后画面的与本帧数据的叠加取得最终的画面。B帧压缩率高,但是解码时CPU会比较累。 B帧的预测与重构 B帧以前面的I或P帧和后面的P帧为参考帧,“找出”B帧“某点”的预测值和两个运动矢量,并取预测差值和运动矢量传送。 接收端根据运动矢量在两个参考帧中“找出(算出)”预测值并与差值求和,得到B帧“某点”样值,从而可得到完整的B帧。 B帧特点 1.B帧是由前面的I或P帧和后面的P帧来进行预测的; 2.B帧传送的是它与前面的I或P帧和后面的P帧之间的预测误差及运动矢量; 3.B帧是双向预测编码帧; 4.B帧压缩比最高,因为它只反映丙参考帧间运动主体的变化情况,预测比较准确; 5.B帧不是参考帧,不会造成解码错误的扩散。 注:I、B、P各帧是根据压缩算法的需要,是人为定义的,它们都是实实在在的物理帧。 一般来说,I帧的压缩率是7(跟JPG差不多),P帧是20,B帧可以达到50。 可见使用B帧能节省大量空间,节省出来的空间可以用来保存多一些I帧,这样在相同码率下,可以提供更好的画质。 8、压缩算法的说明 h264的压缩方法: 1.分组:把几帧图像分为一组(GOP,也就是一个序列),为防止运动变化,帧数不宜取多。 2.定义帧:将每组内各帧图像定义为三种类型,即I帧、B帧和P帧; 3.预测帧:以I帧做为基础帧,以I帧预测P帧,再由I帧和P帧预测B帧; 4.数据传输:最后将I帧数据与预测的差值信息进行存储和传输。 9、帧内(Intraframe)压缩也称为空间压缩(Spatial compression)。当压缩一帧图像时,仅考虑本帧的数据而不考虑相邻帧之间的冗余信息, 这实际上与静态图像压缩类似。帧内一般采用有损压缩算法,由于帧内压缩是编码一个完整的图像,所以可以独立的解码、显示。 帧内压缩一般达不到很高的压缩,跟编码jpeg差不多。 10、帧间(Interframe)压缩的原理是:相邻几帧的数据有很大的相关性,或者说前后两帧信息变化很小的特点。 也即连续的视频其相邻帧之间具有冗余信息,根据这一特性,压缩相邻帧之间的冗余量就可以进一步提高压缩量,减小压缩比。 帧间压缩也称为时间压缩(Temporal compression),它通过比较时间轴上不同帧之间的数据进行压缩。帧间压缩一般是无损的。 帧差值(Frame differencing)算法是一种典型的时间压缩法,它通过比较本帧与相邻帧之间的差异,仅记录本帧与其相邻帧的差值, 这样可以大大减少数据量。 11、顺便说下有损(Lossy )压缩和无损(Lossy less)压缩。无损压缩也即压缩前和解压缩后的数据完全一致。 多数的无损压缩都采用RLE行程编码算法。有损压缩意味着解压缩后的数据与压缩前的数据不一致。 在压缩的过程中要丢失一些人眼和人耳所不敏感的图像或音频信息,而且丢失的信息不可恢复。 几乎所有高压缩的算法都采用有损压缩,这样才能达到低数据率的目标。丢失的数据率与压缩比有关,压缩比越小,丢失的数据越多, 解压缩后的效果一般越差。此外,某些有损压缩算法采用多次重复压缩的方式,这样还会引起额外的数据丢失。
H264结构 |
1、视频就是图片的连续切换,当一秒钟切换的图片个数大于25(帧率),人眼就感觉是连续的。 2、但是图片往往比较大,一段视频占用非常大的存储空间,因此需要进行压缩。 3、H264编码格式就是为了解决这个问题。H264的功能分为两层: 视频编码层(VCL, Video Coding Layer)和网络提取层(NAL, Network Abstraction Layer)。 注意:VLC是多媒体播放器。 4、H264对一幅图片编码,切割成一个一个的NALU(NAL Unit),一个NAUL中包含一个切片,一个切片包含若干个宏块。 宏块是视频信息的主要承载者,它包含着每一个像素的亮度和色度信息。
OCX注册打开视频 |
1、进程中关闭 iexplorer,regsvr32.exe *32 2、加到可信任站点 3、安装VideoDisplay 4、手动运行regOcxAndSetIE.bat,和 regOcx.bat注册
rtsp有关类的结构 |
1、现在考虑vru,vru从vtdu拉取码流,作为rtsp的客户端,使用vtdu_realvideo vru本身又要回放视频,作为rtsp的服务端,也就是vru_slave 2、mediaNodeI对应连接的一端,rtsp_client关联rtsp连接和rtp连接,同时还要关联码流解析器。 3、rtsp_server在端口9816监听,关联一组rtsp的连接,MediaSession对应一个rtsp连接,也就是关联一组MediaSession。 在RTSPServer::onAccept接收到一个9816的rtsp连接,创建一个MediaSession 一个rtsp连接,支持回放多路视频,MediaSession关联一组mediaNodeI,mediaNodeI对应一个码流回放的连接。 4、那么问题来了, vru使用vtdu的sdk,作为rtsp的客户端,vtdu_sdk需要使用rtsp_client vru_slave作为rtsp的服务端,需要使用rtsp_server 5、中间需要加一层,也就是rtsp_client_kit和rtsp_server_kit 他们是怎么关联的? 6、先考虑Client,vtdu_realvideo关联rtsp_client_kit,rtsp_client_kit关联RtspBase,而vtdu_realvideo继承RtspBase rtsp_client_kit关联rtsp_client,rtsp_client关联IRtspEvent,而rtsp_client_kit继承IRtspEvent 7、再考虑Server,vru_salve关联rtsp_server_kit,rtsp_server_kit关联RtspBase,而vru_salve继承RtspBase rtsp_server_kit关联rtsp_server,rtsp_server关联IRtspEvent,而rtsp_server_kit继承IRtspEvent
rtsp穿网包 |
1、为什么发穿网包? 考虑下面的场景,客户端在内网,服务端在外网,服务端向客户端发送码流,不能成功,因为客户端的NAT会丢弃掉码流包。 2、解决办法有: 办法一:在客户端的NAT上设置DNAT,但是客户端接收码流的UDP端口是变化的,不具备操作性。 办法二:客户端向服务端发送一个穿网包,进行内网穿透,然后服务端就可以发送码流到客户端。 3、什么时候发穿网包? a、客户端先向服务端请求rtsp地址(9810端口),获取回来的rtsp地址是9816端口。 b、客户端连接rtsp地址,连接成功后。获取一个自增的udp端口(比如20001),用于码流的接收. 通过setup告诉服务端我的端口20001。 c、服务端收到setup请求之后,先保存请求,获取一个自增的udp端口(比如30001),用于码流的发送。 然后去绑定udp端口30001,绑定成功之后,找到setup请求,发送setup回复,告诉客户端我的端口是30001。 d、客户端收到setup回复,然后绑定20001,绑定成功以后,发送穿网包(从客户端的20001端口向服务端的30001发送穿网包) 这样,服务端从30001向客户端20001发送码流就能成功。 e、客户端端口绑定成功,发送穿网包之后,回调onRtpConnect上来,表示客户端已经准备好,然后发送play 等待接收码流。 f、停止播放,客户端发送teardown 4、怎么发送穿网包? 发送什么内容不重要,随便发几个字节都行,但是为了规范性,发送 m_pRtp->send("tunnel", strlen("tunnel")); 另外,这个穿网包使用udp,可能会丢包,因此要多发送几次。
vru码流数据的发送 |
1、从cqfs读取的码流,带有dh头,帧的开头是DHAV,帧的结尾是dhav,里面是H264的码流 2、发送码流有两种方式:发给testclient和vlc 3、发给testclient,直接把带有dh头的数据,切割,发送出去,发送的数据包含dh头。 4、发给vlc,去掉dh头,逐个读取naul,去掉naul头,把naul的内容取出来,把naul作为基本单位,进行发送。 如果naul的内容太长,要分成多次发送。 注意:可以按照naul为基本单元进行发送(发给vlc),也可以把naul转化为ps流进行发送(在南网发给上级域,因为要求是ps流)。
vtdu打开视频的参数 |
1、vtdu打开视频的接口,参数有 码流打包类型 typedef enum { eSPT_RTP_SDK = 0 , //用对于的制造商sdk来解码 (设备排序RTP码流) eSPT_RTP_H264_RAW , //rpt h264 裸码流 (H264RTP裸码流) eSPT_RTP_H264_NAL , //rpt h264 nal打包 (H264RTP标准码流) eSPT_PG_PSS, //pg pss流 (南方电网PG码流) }enumStreamPackType; 传输协议 typedef enum { ST_TCP, ST_UDP, ST_RTSP }enumTransProto; 2、调用打开视频的接口,设置peStartRVideoRequest的这些字段,然后根据peStartRVideoRequest设置VtduRealVideo的这些字段 3、然后根据enumStreamPackType和enumTransProto,设置RtspClientKit的RtpType,如下: RtpType rtpType = RTP_OVER_UDP; if (m_transType == ST_TCP) { rtpType = RTP_OVER_TCP; if(m_streamPackType == eSPT_PG_PSS) { rtpType = OTHERS_OVER_TCP; } } else { if(m_streamPackType == eSPT_PG_PSS) { rtpType = OTHERS_OVER_UDP; } } m_pRtspClient = new RtspClientKit(m_streamPackType, m_realHandle, rtpType); 4、RtspClientKit 根据m_streamPackType设置码流解析器,如下: if (m_streamPackType >= eSPT_RTP_SDK && m_streamPackType <= eSPT_RTP_H264_NAL) { RtpStreamParseType nParseType = (RtpStreamParseType)m_streamPackType; RtpStreamParser *parser = new RtpStreamParser; parser->SetVideoRtpType(100); parser->SetVideoStreamParseType(nParseType); parser->SetAudioRtpType(m_audioPayload); if (m_audioType == DH_AUDIO_G711U) { parser->SetAudioStreamParseType(STREAM_ORG_G711U); } else { parser->SetAudioStreamParseType(STREAM_ORG_G711A); } m_strmPrs = parser; } else if(eSPT_PG_PSS == m_streamPackType) { // [25697] 根据客户端的配置,设置StreamParser m_strmPrs = new PgStreamParser; } 5、RTSPClient根据m_type,设置多媒体解析器,如下: mediaParserI *pParser = NULL; if (m_type == RTP_OVER_TCP || m_type == RTP_OVER_UDP) { pParser = new mediaRTPParserI; mediaRTPParserI *pR = (mediaRTPParserI *)pParser; pR->setRTPTTL(2); pR->setExconnect(1, 1); } else { pParser = new mediaPgParser; } 6、RTSPClient从udp收到码流,使用多媒体解析器(比如mediaPgParser继承mediaParserI)解析,回调给RtspClientKit 然后RtspClientKit处理多媒体数据包,如下: void RtspClientKit::onPacket(t_uint32 nId, mediaPacketI *pPacket) m_strmPrs->InputPacket(pPacket); 可以认为RtspClientKit使用码流解析器(比如PgStreamParser继承StreamParser)把多媒体数据包重组,然后回调给VtduRealVideo。如下: ret = m_pRtpEvent->onRealData(p, len, m_pRtpUser); 7、注意client,vtdu,vru之前的关系 client会打开vtdu的视频 client会打开vru的视频,回放视频 vru会打开vtdu的视频,保存录像 vtdu会打开vru的视频,在级联的情况下,当前域的vtdu从vru拉码流,转发给上级域的vtdu
- 参见
上墙有关的问题 |
1、视频轮巡,停留时间相同可以轮巡,停留时间不同不能轮巡。 原因是:添加解码器的时候,streammode不对,导致报错。时间相同,和时间不同二者的流程不一样。 表示从平台拉码流,有两个地方:一个添加设备的时候streamMode【2】,一个是上墙任务visitorMode【取值2】 2、nvd0405不支持自由分割,由上墙任务的screenMode来体现。 screenMode 分割模式(1)是规则分割,top left width height参数忽略 开窗模式(2)是自由分割,必须填写正确的top left width height 3、融合的控制 配置墙的时候,按照融合的方式进行。 添加解码器的时候,表示是否支持融合,在任务上墙的时候,设置isCombined字段。 4、解码器的登录信息 ssh的用户名和密码是 admin 7ujMko0admin 如果修改了密码,比如修改为 xxx,那么密码为7ujMko0xxx nvd0405不支持ssh,只有telnet,取日志必须使用SecureCRT连接,session获取日志。 5、解决nvd0405上墙的问题 使用目前的vms版本 vms_2.3X_532950测试是可以的。 但是必须升级nvd0405。 按照邓世强的意思,如果不升级,修改上墙任务的screenmode为1(表示规则分割)应该也是可以的,没有测试。
作为客户端理解rtsp流程 |
1、作为client,rtsp连接成功之后,使用异步集成接口VTDU_A_Rtsp_I发送setup,并设置码流回调 发送setup,告诉server我的udp端口,从server收到setup的回复,server会告诉我,它的udp端口 这个时候client向server发送udp穿网包 2、作为client,发送穿网包之后,回调上来udp的onRtpConnect,检查是不是集成接口,如果是再发play。 如果不是集成接口,需要client再发送play 3、那么什么时候发送穿网包呢? rtsp_kit工程引用rtsp_rtp工程。 RtspServerKit关联RTSPServer RtspClientKit关联RTSPClient 实时视频走rtsp协议,作为服务端关联RtspServerKit,作为客户端关联RtspClientKit 无论是客户端还是服务端,都继承RtspBase,通过RtspServerKit或者RtspClientKit设置回调,执行onMessage 从server收到setup的回复,执行onMessage-->onHandleResponse-->RtspClientKit::startRtp -->RTSPClient::setUp-->mediaNodeI::open发送穿网包
- 参见
如何查看视频的编码类型 |
1、使用SPDemo.exe,把视频文件拖进去,就能看到。 2、使用电子眼 eseye_u.exe,也可以查看视频的编码格式。 3、常用的编码格式有 H.26X系列和MPEG系列,另外一些不常用的编码格式有WMV和VC-1 4、常见的音频格式有G711A、G711U、PCM8、PCM16、IMA_ADPCM等。
播放抓包得到的码流数据 |
情况一:TCP交互rtsp信令,通过UDP发送码流 1、找到码流的UDP数据报,右击选择Decode As,协议选择RTP,然后Apply 2、然后Follow UDP Stream,选择C Arrays,然后Save As,保存起来为aaa 3、运行StreamData.exe,点击RTP(C Arrow),选择文件aaa,然后产生一堆文件 4、运行Elecard StreamEye Tools目录中的eseye_u.exe,将aaa.checkmap.dav拖进去 5、可以看到I帧、P帧。点击播放按钮即可 6、对于自己保存的码流文件也是一样。vru从vtdu获取到码流,保存到文件,这个文件也可以播放 情况二:TCP交互rtsp信令并且发送码流 1、找到tcp连接,Follow TCP Stream,在选择raw的情况下,Save As为111 2、运行TcpRtpParser.exe,将111拖进去, 3、然后播放111_nortp.dav 注:软件做个备份,包括StreamData.exe,TcpRtpParser.exe 和 Elecard StreamEye Tools 这些工具在软件目录中
浓缩视频回放的问题 |
1、考虑下面的问题,浓缩视频回放,定时读取一帧数据。开始的解决办法是: 第一次读取整个浓缩文件,把内容写到工具类,然后从里面读取数据帧。每次返回一帧数据。 2、上面的方式是提前把数据帧都准备好。但是这样存在问题: 第一个问题,浓缩视频文件很大,读取整个文件耗时很多,导致获取第一帧耗时很多,客户端收到第一个码流需要很长时间,超时。 第二个问题,回放的时候,会导致内存使用突然间变很大,然后一点一点释放,一起回放多个,内存不够用,崩溃。 3、怎么解决上面的问题? 解决思路是分摊读取浓缩视频文件的开销。理想的情况是:按需读取,每次读取一帧数据出来。 但是这会增加代码的复杂度,有没有更好的解决办法? 4、每次获取一帧的时候,从浓缩视频文件中读取一定的数据,保证读取的数据大于最大的帧大小。 但是,一般情况下,每一帧的数据大小差别很大,I帧会有200多K,P帧可能只有10K,平均是20K左右。 因为一个I帧后面往往跟着20个左右的P帧。 很极端的情况下,一个I帧有2M,每次都读取2M,还是导致问题,读取的太快。 5、怎么解决? 第一次读取,先读取16M缓冲起来,以后每次读取平均值(20K)的数倍,比如128K。 这样的话,一方面,避免了极端情况下,读取数据不够一帧的问题,同时又很大程度上,分摊了读取的开销。 6、示例代码如下: DhFrame* Channel::readFrameFromSynopsis() { if(fileHandleForRead == NULL) // 第一次,打开文件 { fileHandleForRead = fopen(m_synopsisFilePath.c_str(), "rb"); // 读取文件 VruLogWarning("Channel[%p] Read File[%s] FileHandle[%p] Then Send To Client", this, m_synopsisFilePath.c_str(), fileHandleForRead); // 防止极端情况,第一帧特别大,先读取16M缓冲起来 int buffSize = 1024*1024*16; uint8_t* buff = new uint8_t[buffSize]; memset(buff,0,buffSize); int readed = fread(buff,1,buffSize,fileHandleForRead); VruLogInfo("Channel[%p] Read File[%s] FileHandle[%p] ReadSize[%d]", this, m_synopsisFilePath.c_str(), fileHandleForRead, readed); DhStream dhStream; dhStream.Write(buff, readed); dhStream.Read(frameListFromSynopsisFile); delete[] buff; } if(feof(fileHandleForRead) == false) // 一次读一些 { // SizeForOneRead = 1024*128 memset(m_buffReadSynopsisFile,0,SizeForOneRead); int readed = fread(m_buffReadSynopsisFile,1,SizeForOneRead,fileHandleForRead); VruLogDebug("Channel[%p] Read File[%s] FileHandle[%p] ReadSize[%d]", this, m_synopsisFilePath.c_str(), fileHandleForRead, readed); DhStream dhStream; dhStream.Write(m_buffReadSynopsisFile, readed); dhStream.Read(frameListFromSynopsisFile); } // 每次返回一帧 if(frameListFromSynopsisFile.size() > 0) { DhFrame* dhFrame = frameListFromSynopsisFile.front(); frameListFromSynopsisFile.pop_front(); return dhFrame; } VruLogWarning("Get Frame Over"); // 返回所有的帧 this->m_rtspState = RTSP_INIT; return NULL; } Channel析构的时候,进行fclose(fileHandleForRead);
理解rtsp_rtp_rtcp |
RTP: Real-time Transport Protocol,实时传输协议,一般用于多媒体数据的传输。 RTCP: RTP Control Protocol,实时传输控制协议,同RTP一起用于数据传输的监视,控制功能。 RTSP: Real Time Streaming Protocol,实时流协议,用于多媒体数据流的控制,如播放,暂停等。 RTP/RTCP相对于底层传输层,和RTSP,SIP等上层协议一起可以实现视频会议,视频直播等应用。 rtsp发起/终结流媒体(通过sdp) rtp传输流媒体数据 rtcp对rtp进行控制,同步。注意:rtcp的端口一般是rtp的端口+1 RTSP的请求主要有DESCRIBE,SETUP,PLAY,PAUSE,TEARDOWN,OPTIONS等,顾名思义可以知道起对话和控制作用 RTP/RTCP是实际传输数据的协议 RTP传输音频/视频数据,如果是PLAY,Server发送到Client端,如果是RECORD,可以由Client发送到Server RTCP包括Sender Report和Receiver Report,用来进行音频/视频的同步以及其他用途,是一种控制协议 RTSP的对话过程中SETUP可以确定RTP/RTCP使用的端口,PLAY/PAUSE/TEARDOWN可以开始或者停止RTP的发送,等等
理解rtsp的流程 |
1、客户端使用vru_sdk,调用集成接口VRU_AC_StartVideo_I,向vru请求rstp 2、VruSession收到rtsp的回复,检查是集成接口,使用VruRealVideo::Connect(msg, ASYN_INTEGRATE); VruRealVideo使用RtspClientKit::startRtsp(m_strUrl, 5000); 连接rtsp 3、以client为例,RtspClientKit继承IRtspEvent,而RTSPClient关联IRtspEvent RtspClientKit在创建RTSPClient的时候,把this指针传给RTSPClient, RTSPClient执行onConnect调用onRtspConnect,多态执行到RtspClientKit的方法 4、VruRealVideo继承RtspBase,而RtspClientKit关联RtspBase 而VruRealVideo通过m_pRtspClient->setCallback(this, this);设置回调把this指针传过去 多态执行到VruRealVideo 5、rtsp连接成功,执行到VruRealVideo::onRtspConnect,检查是集成接口,向vru发送describe和setup 6、服务端也能收到onRtspConnect,VruSlave继承RtspBase,但是没有重写onRtspConnect 7、VruSlave继承RtspBase,s_pRtspServer->setRtspCallback(this, this); 把this指针传递过去。 客户端发送describe和setup,服务端从RtspServerKit回调上来,onMessage 8、对于请求setup,打开rtp有关的udp端口,保存setup请求。 打开udp端口(只是打开端口,并没有连接),会从RtspServerKit回调上来udp的connect,然后 ORTSPServer::OnRtpConnect-->RtspServerKit::onRtpConnect-->VruSlave::onRtpConnect 找到setup的请求,向客户端发送setup回复 9、VruRealVideo收到setup的回复,执行onMessage,处理setup的回复, RtspClientKit::startRtp( m_remoteRtpIp, m_remoteRtpPort+nAdd); --> RTSPClient::setUp(rIp, rPort, m_localIp, m_localPort); --> mediaNodeI::open((t_int8 *)rIp.c_str(), rPort, "0.0.0.0", lPort, type) 作为udp的客户端,需要指定远程的ip和port,以及作为udp客户端,本地的端口 发送穿网包,发送穿网包之后回调上来。 然后执行onRtpConnect,再去发送play 10、注意:对于rtsp的流程都是在RtspBase的子类中处理的,重写onMessage rtsp的打包解析协议是rtsp_protocol,class CRtspSetUpRequest:public peRtspSetupRequest pe协议是载体,暴露出结构体的字段,打包解析协议继承pe协议,对应不同的打包解析方式。 dhtp是内部默认的打包解析协议
理解我们的rtsp流程【客户端】 |
1、以vru为例,vru录像会从vtdu拉取码流,看看这个过程。 2、vru收到录像请求,master线程向vtdu请求rtsp地址(通过与vtdu的9810连接) 3、从vtdu获取到rtsp地址,找到原始录像请求,将rtsp的有关信息(比如:rtsp://172.16.2.20:9816/realplay/2?token=2), 设置到录像请求协议中,转发给salve线程。注意:rtsp的端口是9816 4、slave线程收到录像请求,判断是前端录像还是中心录像。 前段录像录在DVR或者NVR上面 中心录像录在平台上面,比如vru从vtdu拉取码流,保存在平台上面 DVR:Digital Video Recorder 数字硬盘录像机,也就是在口子上接入IPC设备 NVR:Network Video Recorder 网络硬盘录像机,通过网络接入IPC设备 5、如果是中心录像,登录vtdu的rtsp地址【VTDU_A_RtspConnect4Server】,返回一个登录句柄,使用这个登录句柄进行rtsp交互。 设置回调rtsp的回调【VTDU_A_SetRtspMsgCallBack】 6、收到rtsp连接成功的回调,构造setup请求,调用rtsp的异步集成接口【VTDU_A_Rtsp_I】 同时设置码流回调【VTDU_A_SetRealDataCallBack】 7、现在分析setup的异步集成接口 a、在调用VTDU_A_RtspConnect4Server连接vtdu【9816】的时候,初始化本地的rtp端口 b、先发送setup请求,里面带着本地的rtp端口 c、然后再onMessage处理setup的回复,先设置码流解析器,并且在本地绑定rtp端口,用于接收码流 d、端口绑定成功,回调onRtpConnect,检查是异步集成接口,发送play 8、停止录像的时候,master线程向vtdu【端口9810】发送peCloseRealVideoRequest 同时转发给slave线程,slave向vtdu【端口9816】发送teardown
理解我们的rtsp流程【服务端】 |
1、rtsp走控制信令,setup沟通端口,用于码流的发送,play发送,teardown关闭请求 2、先看rtsp服务端,以vru为例 3、Rtsp_Server在9836监听,当客户端连接成功【onAccept】,创建一个new MediaSession(nSeq); nSeq为自增Id,用于标识new MediaSession MediaSession用于标识一个rtsp连接(也就是客户端连接9836),Rtsp_Server关联一组MediaSession 4、在rtsp的服务端收到setup请求,先保存请求,然后在本地打开端口(用于传输码流),打开成功之后,找到原始请求,返回给客户端。 打开rtp的端口,在MediaSession::onConnect 然后判断出rtp,调用m_pEvent->onRtpConnect 注意:服务端的端口打开,客户端就会发送穿网包,内容是tunnel 5、在打开本地端口的时候,创建mediaNodeI,用于码流的发送,mediaNodeI对应一路码流,MediaSession关联一组mediaNodeI 也就是说,一个rtsp连接可以回放多路视频。MediaSession和mediaNodeI确定一路视频。
理解码流有关的概念 |
1、H264是视频的编码方法,是一种标准的格式。 2、在H264的码流数据上,可以加上不同的头部信息,可以加上大华头部(DHAV),变成大华码流。 3、大华码流(大华头+H264)必须识别这种格式才能播放。大华码流不是标准的打包格式。 4、PS流是标准的打包格式,大家都认识。在PS流上再封装一层PG头,这就是南方电网使用的PG+PS流
理解采样率 |
1、声音是由于振动产生的。想象出一个振膜,随着时间上下振动。 2、因此,音频信息可以使用振膜的位置来表示,相对于时间,这个位置是连续的。 但是计算机没法表示连续的值,只能表示离散值。这些离散值就是每隔一段时间,振膜的位置。 当离散值足够多的时候,就能够基本上还原出音频。离散值越多,还原的越接近,也就是音质越好,但是需要的存储空间越大。 3、每秒离散值的个数就是采样率,采样率是个折中,当人的耳朵分辨不出来微小的区别,就够了。 4、对于视频,也是同样的道理。要表示一组连续的图片,也需要采集这些信号。 采样率就是1秒钟采集多少个样本。 5、网络上发包,需要表示两个音频数据包或者视频数据包的前后,使用时间戳来表示前后顺序。 这个时间戳是逻辑概念,不表示真实的时间,与unix时间戳不同。 因此可以使用采样的次数表示。 6、考虑音频,采样率是8000,20毫秒发送一个音频数据包。两个音频数据包的时间间隔(逻辑上的时间戳),可以使用采样次数表示。 也就是1秒钟有1000/20=50个音频包,每个音频包的采样次数 (也就是两个音频的间隔)是8000/50=160 7、考虑视频,采样率是90000,视频播放的时候,帧率是25,一秒钟播放25个画面,那么视频数据包的时间戳可以使用, 两个数据包间隔的采样次数来表示,也就是一个画面对应多少采样次数。 90000/25=3600采样次数
码流有关的端口 |
1、码流的传输使用udp,rtp有个服务端和客户端,二者都有个端口范围。 2、以vtdu为例,客户端在vtdu_sdk设置端口范围,如下: t_int32 funcClub::VTDU_Func_Init() { logI log; log.regMod(VTDU_SDK);//注册日志模块 VtduRealVideo::getPortManager()->setPortRange(30000, 33000, 2); int ret = VtduSdkManager::instance()->VtduSdkInit(); streamTransInit(); return ret; } 3、服务端使用配置文件,如下: <send_stream_first_port>20000</send_stream_first_port> <send_stream_last_port>30000</send_stream_last_port> 并且设置 pPortManager->setPortRange(portBegin, portEnd, 2); 4、特别注意:在同一台设备获取码流,rtp的客户端与服务端,udp的端口范围必须不一样。 否者,端口范围一样,打开端口失败,无法获取码流。 5、rtp客户端发送setup请求,填写client的ip和端口 rtp服务端回复setup,填写server的ip和端口
网页中嵌入播放器控件 |
1、比如嵌入VLC控件,如下: <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>RTSP</title> </head> <body> <object id="vlc" classid="clsid:9BE31822-FDAD-461B-AD51-BE1D1C159921" width="500" height="500" events="True"> <param name="AutoLoop" value="0" /> <param name="AutoPlay" value="-1"/> <param name="Toolbar" value="0"/> <param name="ExtentWidth" value="21167"/> <param name="ExtentHeight" value="11906"/> <param name="MRL" value="rtsp://10.36.65.80:5836/playback?domid=70283&chncode=AC19E569-4972-E56B-0002-13F8F1DAE413&begintime=20170117T165514Z&endtime=20170117T165552Z"/> <param name="Visible" value="-1" /> <param name="Volume" value="50"/> <param name="StartTime" value="0"/> <param name="BackColor" value="0"/> </object> </body> </html> 2、这里起作用的关键一点是classid="clsid:9BE31822-FDAD-461B-AD51-BE1D1C159921",就是windows中注册的控件ID clsid就是classID类的标识码,是控件的身份证号码。 注:这是在windows中注册的,只能使用IE打开这个控件,使用google浏览器是不行的。 3、怎么找到VLC对应的clsid? a、运行regedit b、选择HKEY_CLASSES_ROOT,快速输入CLS,定位到目录CLSID c、查找,选择项,查找关键字VLCPlugin,注:F3是下一个的快捷键,没有上一个的快捷键 d、可以找到对应的项,有一个子目录就是CLSID,打开就能看到数值数据,是一个字符串。
Copyright (c) 2015~2016, Andy Niu @All rights reserved. By Andy Niu Edit.