zoukankan      html  css  js  c++  java
  • GB28181平台实现,支持摄像头公网WEB端直播

    背景

    28181协议全称为GB/T28181《安全防范视频监控联网系统信息传输、交换、控制技术要求》,是由公安部科技信息化局提出,由全国安全防范报警系统标准化技术委员会(SAC/TC100)归口,公安部一所等多家单位共同起草的一部国家标准(以下简称28181)。

    28181协议在全国平安城市、交通、道路等监控中广泛采用,若想做统一的大监控平台,则支持28181协议接入是必不可少的。如今很多客户都是想在之前使用的28181平台的基础上进行拓展。

    说明

    LiveGBS GB28181流媒体服务器负责将GB28181设备/平台推送的PS流转成ES流,然后进行分发。
    同时,LiveGBS 对外提供HTTP API接口,通过接口可以获知流媒体转发服务的运行状态信息,转发会话信息,服务器配置和版本信息等;

    LiveGBS GB28181流媒体服务器提供以下功能:
    1. 接受和处理GB28181接入服务器的推流请求(如有推流权限验证则调用验证服务器接口);
    2. 接受和处理GB28181设备的推流;
    3. 实时流媒体处理,PS(TS)转ES;
    4. 推送ES流到EasyDSS流媒体服务器;
    5. 接受和处理GB28181接入服务器的断开推流请求;
    6. 对外提供服务器获取状态、信息,控制等http API接口;

    LiveGBS流媒体直播详细流程

    1 接入服务器发送Invite请求
    接入服务器向流媒体服务器发送Invite请求,请求流媒体服务返回携带SDP 消息体,消息体中
    描述了媒体服务器接收媒体流的IP、端口、媒体格式等内容;
    Invite请求代码如下:

                const options = {
                    serialServer: serialServer,
                    serialDevice: code,
                    method: common.SIP_INVITE,
                    contentType: common.CONTENT_NONE,
                    content: sdp,
                    host: hostip,
                    port: hostport,
                    rtpovertcp: (parseInt(rtpovertcp)===0?'UDP':'TCP')
                };
                console.log('inviteMediaServer......sendRequest' + JSON.stringify(options));
                uas.sendRequest(options);
    

    2 流媒体服务接受Invite请求处理并ACK应答
    流媒体服务接受Invite请求,并在回调函数中处理请求,js代码如下:

            uas.on('invite', async ctx => {
                const request = ctx.request;
                const content = JSON.parse(request.content);
                const status = 200;
                const serial = sip.parseUri(request.uri).user;
                const host = config.server.serverHost;
                let ssid = serial.substring(16,20);// PrefixInteger(sessionid,4);
                let sirialid = serial.substring(3,8);
                const ssrc = "0"+sirialid+ssid;     
                console.log("ssrc = "+ssrc);
                let sdp = '';
    
                //如果已存在     
                let bHas = this.session_.has(serial);
                console.log(bHas);
                if (bHas) {
                    console.log('this.session_ has exist serial: '+serial);
                    sdp = '';
                }           
                else{           
                    let port = config.server.udpPort;//流媒体接收TCP端口
                    let transport = 'RTP/AVP';
                    let a = "a=recvonly
    ";
                    if(content.rtpovertcp === 'TCP' )
                    {
                        port = config.server.tcpPort;//流媒体接收TCP端口
                        transport = 'TCP/RTP/AVP';  
                        a = "a=recvonly
    a=setup:passive
    ";       
                    }
                    sdp = "v=0
    " +
                    `o=${serial} 0 0 IN IP4 ${host}
    ` +
                    "s=Play
    " +
                    `c=IN IP4 ${host}
    ` + 
                    "t=0 0
    " +
                    `m=video ${port} ${transport} 96 98 97
    ` +
                    "a=rtpmap:96 PS/90000
    " +
                    "a=rtpmap:98 H264/90000
    " + 
                    "a=rtpmap:97 MPEG4/90000
    " +               
                    `${a}`+
                    //`a=connection:new
    ` +
                    `y=${ssrc}
    `;
                    // A new channel is coming, delete the old
                    rtpserver.deleteChannels(parseInt(ssrc));
                    // Create a new stram,and add to redis
                    this.registerStream(parseInt(ssrc),uuidv4(),true);                
                }
                let response = sip.makeResponse(request, status, common.messages[status]);
                uas.sendAckEx(response, sdp);
            });
    

    如上代码所示,我们在SDP消息体中提供了两种流传输方式,分别是TCP和UDP,通过Invite请求所带的 “rtpovertcp ”参数来控制,TCP方式因为其不丢包的传输方式在GB28181设备推流到公网服务器的方案中得以广泛应用,然而,目前市面上的多数支持国标的设备都不支持tcp模式推流,udp仍然是主流的推流方式,不过,经测试udp推流方式在公网应用中效果比较差,需要进一步优化或者改进。

    3 接入服务器接收ACK应答并Invite请求设备开始推流
    回调函数中ack应答处理js代码如下:

             uas.once('ack', async ctx => {
                    const request = ctx.request;
                    const callId = request.headers['call-id'];
                    if (request.content.length > 0 ) 
                    {
                        const serial = serialDevice;//sip.parseUri(request.headers.from.uri).user;
                        let response ;
                        if(!this.session_.has(callId))
                        {
                            response = await this.inviteDevice(serial, code, callId, request.content);
                            //Invite Device is complete
                            if(response != undefined)
                            {
                                if(response.content)
                                {
                                    const transform = require('sdp');
                                    const res = transform.parse(response.content);
                                    console.log(res.media[0].protocol);
                                    if((res.media[0].protocol === 'RTP/AVP'&&parseInt(rtpovertcp)===0) || 
                                        (res.media[0].protocol === 'TCP/RTP/AVP'&&parseInt(rtpovertcp)===1) ){
                                        if (response.status === 200 ) 
                                        {
                                            //send ack to stream server
                                            this.ackMediaServer(response.status,request,request.content);               
                                            this.session_.set(callId, response);
                                        }
                                    }
                                    else{
                                        response.status = 700;
                                    }
                                }
                                console.log('inviteMediaServer ack is coming.......response='+JSON.stringify(response));
                            }
                            resolve(response);
                        }
                        else{
                            console.log('inviteMediaServer this.session_.has: '+callId);
                        }
                    }
                });
    

    如上代码所示,在InviteDevice请求完成后,我们在返回Response处理过程中做过一次特殊处理,即:如果TCP拉流时发现设备拉流应答中返回其推流模式依然是'RTP/AVP'的UDP模式,我们认为其设备不支持TCP模式,从而向上层返回700,不支持的流媒体传输方式。

    4 Invite设备正常返回200应答并传递给流媒体服务器
    代码在第3点中有所体现。

    5 流媒体服务接受拉流请求成功应答

            uas.on('ack', async ctx => {
                const request = ctx.request;
                if (request.content.length === 0) {
                    return;
                }
                const serial = sip.parseUri(request.headers.from.uri).user;
                this.session_.set(serial, request);
                const ssrc = serialTossrc(serial);
                // resole a new stram,and refresh to redis  
                const info = JSON.parse(await redis.get(`stream:${parseInt(ssrc)}`)); 
                this.registerStream(parseInt(ssrc),info.uuId,false);     
            });
    

    至此,整个拉流过程已经完成。

    获取更多信息

    下载:https://www.liveqing.com/docs/download/LiveGBS.html

  • 相关阅读:
    IT认证一一看过来
    SQL Server连接中三个常见的错误分析
    解决SFTP时,NetBeans恼人的RSA提示
    Mixing Integrated Authentication and Anonymous Authentication with PreAuthenticated = true doesn’t work
    一段扫flash跨站的脚本
    图解用WAS对Web服务器进行压力测试
    Google TrustRank与Hilltop算法
    Stupid smart code
    Archlinux桌面配置指南
    TSVNCache占用CPU的解决办法
  • 原文地址:https://www.cnblogs.com/kumukim/p/11088102.html
Copyright © 2011-2022 走看看