Andy Niu �����ĵ�

Andy Niu

Andy Niu Help  1.0.0.0
视频音频

变量

 上墙有关的问题
 
 理解采样率
 
 理解码流有关的概念
 
 播放抓包得到的码流数据
 
 H264码流数据帧
 
 vtdu打开视频的参数
 
 码流有关的端口
 
 作为客户端理解rtsp流程
 
 理解rtsp的流程
 
 理解rtsp_rtp_rtcp
 
 理解我们的rtsp流程【服务端】
 
 理解我们的rtsp流程【客户端】
 
 rtsp有关类的结构
 
 rtsp穿网包
 
 浓缩视频回放的问题
 
 OCX注册打开视频
 
 H264结构
 
 vru码流数据的发送
 
 网页中嵌入播放器控件
 
 H264_NAUL头的解析
 
 如何查看视频的编码类型
 

详细描述

变量说明

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.