国网B接口调阅实时视频规范解读和代码示例分析

简介: 国网B接口调阅实时视频,相关规范写的比较粗略:调阅实时视频包括信令接口和媒体流接口,采用标准的SIP INVITE+SDP流程,媒体传输使用RTP/RTCP。

接口描述

国网B接口调阅实时视频,相关规范写的比较粗略:


调阅实时视频包括信令接口和媒体流接口,采用标准的SIP INVITE+SDP流程,媒体传输使用RTP/RTCP。


SDP 中 RTP Payload 的取值应遵守下面接口参数中的定义:


a) SDP 中的媒体信息,应仅有一个 m 行,用于描述视频格式。


b) 视频数据用 RTP 打包传输时,应考虑每个传输分组不大于 MTU,可采用的技术包括编码器层支持(如 ITU-T H.264 的 multi-slice 技术),或采用 RTP 层的分片机制(如 IETF RFC 3984 定义的 FU-A 技术)。


前端设备收到平台的INVITE请求后根据SDP描述进行媒体协商,协商通过后打开前端系统摄像机设备将获得的媒体流通过媒体通道发送到平台。


会话建立成功后,前端系统在某些特殊情况下可以主动结束当前呼叫。


平台应支持视频流的分发,以降低对前端系统的操作频繁性和节省网络带宽。


调阅实时视频的接口流程


6c94720af4274467bcd96f735e7954a6.png

主要功能流程如下:


a) F1:用户发送 INVITE 消息,携带 SDP 内容通过平台转发到前端设备。


b) F2:按照 SIP 要求,如前端系统在 0.5s 内未能处理该请求,则先发送 1xx 临时响应通过平台转发到用户。


c) F3:前端系统接受了调阅请求的操作,则发送携带 SDP 的 200 OK 响应通过平台转发到用户。


d) F4:用户发送 ACK 通过平台转发到前端设备。


e) 视频流从前端系统传输经平台转发到用户。


f) F5:用户结束会话,发送 BYE 消息到通过平台转发到前端系统。


g)F6:前端系统发送确认,将媒体通道拆线。

技术实现

由于国网B接口的invite实现和GB28181的差异不大,之前我们GB28181这块,已经有非常好的积累了。

56e3691f65ee414d9e9ccd00684a235e.jpg

启动B接口后,完成平台端的register和PushResourse交互,有些平台注册后,会接着响应Push_Resource request,其他不表,这里主要谈下invte和Ack相关回调处理:


Invite信令如下:

    INVITE sip:1301110005010100001@192.168.0.102:5060 SIP/2.0
    Via: SIP/2.0/UDP 192.168.0.104:15060;branch=z9hG4bK864531896
    From: <sip:000000000000000001@0000000000>;tag=482531896
    To: <sip:1301110005010100001@192.168.0.102:5060>
    Call-ID: 804531783
    CSeq: 6 INVITE
    Content-Type: application/sdp
    Contact: <sip:000000000000000001@192.168.0.104:15060>
    Max-Forwards: 70
    User-Agent: SIPB
    Request-URI: <sip:1301110005010100001@192.168.0.102:5060>
    Content-Length: 152
    v=0
    o=- 0 0 IN IP4 192.168.0.104
    s=Play
    c=IN IP4 192.168.0.104
    t=0 0
    m=video 30004 RTP/AVP 100
    a=recvonly
    a=rtpmap:100 H264/90000
    y=0130111000

收到Invite回调处理逻辑如下:

    @Override
    public void ntsOnInvitePlay(String deviceId, SessionDescription session_des) {
        handler_.postDelayed(new Runnable() {
            @Override
            public void run() {
                // 先振铃响应下
                gb28181_agent_.respondPlayInvite(180, device_id_);
                MediaSessionDescription video_des = null;
                SDPRtpMapAttribute ps_rtpmap_attr = null;
                Vector<MediaSessionDescription> video_des_list = session_des_.getVideoPSDescriptions();
                if (video_des_list != null && !video_des_list.isEmpty()) {
                    for(MediaSessionDescription m : video_des_list) {
                        if (m != null && m.isValidAddressType() && m.isHasAddress() ) {
                            video_des = m;
                            ps_rtpmap_attr = video_des.getPSRtpMapAttribute();
                            break;
                        }
                    }
                }
                if (null == video_des) {
                    gb28181_agent_.respondPlayInvite(488, device_id_);
                    Log.i(TAG, "ntsOnInvitePlay get video description is null, response 488, device_id:" + device_id_);
                    return;
                }
                Log.i(TAG,"ntsOnInvitePlay, device_id:" +device_id_+", is_tcp:" + video_des.isRTPOverTCP()
                        + " rtp_port:" + video_des.getPort() + " ssrc:" + video_des.getSSRC()
                        + " address_type:" + video_des.getAddressType() + " address:" + video_des.getAddress());
                long rtp_sender_handle = libPublisher.CreateRTPSender(0);
                if ( rtp_sender_handle == 0 ) {
                    gb28181_agent_.respondPlayInvite(488, device_id_);
                    Log.i(TAG, "ntsOnInvitePlay CreateRTPSender failed, response 488, device_id:" + device_id_);
                    return;
                }
                gb28181_rtp_payload_type_  = 100;
                gb28181_rtp_encoding_name_ =  "PS";
                libPublisher.SetRTPSenderTransportProtocol(rtp_sender_handle, video_des.isRTPOverUDP()?0:1);
                libPublisher.SetRTPSenderIPAddressType(rtp_sender_handle, video_des.isIPv4()?0:1);
                libPublisher.SetRTPSenderLocalPort(rtp_sender_handle, 0);
                libPublisher.SetRTPSenderSSRC(rtp_sender_handle, video_des.getSSRC());
                libPublisher.SetRTPSenderSocketSendBuffer(rtp_sender_handle, 2*1024*1024); // 设置到2M
                libPublisher.SetRTPSenderClockRate(rtp_sender_handle, 90000 /*ps_rtpmap_attr.getClockRate()*/);
                libPublisher.SetRTPSenderDestination(rtp_sender_handle, video_des.getAddress(), video_des.getPort());
                if ( libPublisher.InitRTPSender(rtp_sender_handle) != 0 ) {
                    gb28181_agent_.respondPlayInvite(488, device_id_);
                    libPublisher.DestoryRTPSender(rtp_sender_handle);
                    return;
                }
                int local_port = libPublisher.GetRTPSenderLocalPort(rtp_sender_handle);
                if (local_port == 0) {
                    gb28181_agent_.respondPlayInvite(488, device_id_);
                    libPublisher.DestoryRTPSender(rtp_sender_handle);
                    return;
                }
                Log.i(TAG,"get local_port:" + local_port);
                String local_ip_addr = IPAddrUtils.getIpAddress(context_);
                MediaSessionDescription local_video_des = new MediaSessionDescription(video_des.getType());
                local_video_des.addFormat(String.valueOf(100));
                local_video_des.addRtpMapAttribute(ps_rtpmap_attr);
                local_video_des.setAddressType(video_des.getAddressType());
                local_video_des.setAddress(local_ip_addr);
                local_video_des.setPort(local_port);
                local_video_des.setTransportProtocol(video_des.getTransportProtocol());
                local_video_des.setSSRC(video_des.getSSRC());
                if (!gb28181_agent_.respondPlayInviteOK(device_id_,local_video_des) ) {
                    libPublisher.DestoryRTPSender(rtp_sender_handle);
                    Log.e(TAG, "ntsOnInvitePlay call respondPlayInviteOK failed.");
                    return;
                }
                gb28181_rtp_sender_handle_ = rtp_sender_handle;
            }
            private String device_id_;
            private SessionDescription session_des_;
            public Runnable set(String device_id, SessionDescription session_des) {
                this.device_id_ = device_id;
                this.session_des_ = session_des;
                return this;
            }
        }.set(deviceId, session_des),0);
    }


ack信令如下:

    ACK sip:1301110005010100001@192.168.0.102:5060 SIP/2.0
    Via: SIP/2.0/UDP 192.168.0.104:15060;branch=z9hG4bK991532349
    From: <sip:000000000000000001@0000000000>;tag=482531896
    To: <sip:1301110005010100001@192.168.0.102:5060>;tag=2d6ebb3d
    Call-ID: 804531783
    CSeq: 6 ACK
    Contact: <sip:000000000000000001@192.168.0.104:15060>
    Max-Forwards: 70
    User-Agent: SIPB
    Request-URI: <sip:1301110005010100001@192.168.0.102:5060>
    Content-Length: 0


ack回调代码处理逻辑如下:

    @Override
    public void ntsOnAckPlay(String deviceId) {
        handler_.postDelayed(new Runnable() {
            @Override
            public void run() {
                Log.i(TAG,"ntsOnACKPlay, device_id:" +device_id_);
                if (!isRTSPPublisherRunning && !isPushingRtmp && !isRecording) {
                    InitAndSetConfig();
                }
                libPublisher.SetGB28181RTPSender(publisherHandle, gb28181_rtp_sender_handle_, gb28181_rtp_payload_type_, gb28181_rtp_encoding_name_);
                int startRet = libPublisher.StartGB28181MediaStream(publisherHandle);
                if (startRet != 0) {
                    if (!isRTSPPublisherRunning && !isPushingRtmp  && !isRecording) {
                        if (publisherHandle != 0) {
                            libPublisher.SmartPublisherClose(publisherHandle);
                            publisherHandle = 0;
                        }
                    }
                    destoryRTPSender();
                    Log.e(TAG, "Failed to start GB28181 service..");
                    return;
                }
                startAudioRecorder();
                startLayerPostThread();
                isGB28181StreamRunning = true;
            }
            private String device_id_;
            public Runnable set(String device_id) {
                this.device_id_ = device_id;
                return this;
            }
        }.set(deviceId),0);
    }

总结

国网B接口调阅实时视频流程和GB28181流程基本一致,感兴趣的开发者,可以参考相关的规范实现,B接口相对GB28181来说,面更窄,资料也更少,如果产品化,有测试平台的话,还是不难实现的。

相关文章
|
Linux Android开发 编解码
VLC播放RTSP视频延迟问题
之前写过一篇关于在Linux平台上编译Android平台上VLC播放器源代码的文章,vlc这款播放器非常优秀而且是开源的,它的核心是开源视频编解码库ffmpeg。而且这款播放器还支持RTSP协议,这个主要是用开源的live555来实现的,live555这个库以后还需要认真研习。
6645 0
|
9月前
|
机器学习/深度学习 数据采集 算法
【风电功率预测】【多变量输入单步预测】基于RVM-Adaboost的风电功率预测研究(Matlab代码实现)
【风电功率预测】【多变量输入单步预测】基于RVM-Adaboost的风电功率预测研究(Matlab代码实现)
429 129
|
6月前
|
存储 Linux 网络安全
跨平台文件传输工具FileZilla客户端安装使用教程
跨平台文件传输工具FileZilla客户端安装使用教程
2757 0
|
监控 Java 网络性能优化
容器内存可观测性新视角:WorkingSet 与 PageCache 监控
本文介绍了 Kubernetes 中的容器工作内存(WorkingSet)概念,它用于表示容器内存的实时使用量,尤其是活跃内存。
57898 129
容器内存可观测性新视角:WorkingSet 与 PageCache 监控
|
存储 人工智能 自然语言处理
Elasticsearch Inference API增加对阿里云AI的支持
本文将介绍如何在 Elasticsearch 中设置和使用阿里云的文本生成、重排序、稀疏向量和稠密向量服务,提升搜索相关性。
730 14
Elasticsearch Inference API增加对阿里云AI的支持
|
自然语言处理 JavaScript 前端开发
静态文档网站生成神奇器:VuePress!尤雨溪的旧爱!
【10月更文挑战第3天】静态文档网站生成神奇器:VuePress!尤雨溪的旧爱!
静态文档网站生成神奇器:VuePress!尤雨溪的旧爱!
|
Java Windows
JVM - 一篇带你解决 JConsole 无法本地连接解决方案
JVM - 一篇带你解决 JConsole 无法本地连接解决方案
2558 0
JVM - 一篇带你解决 JConsole 无法本地连接解决方案
|
缓存 监控 API
抖音抖店 API 请求获取宝贝详情数据的调用频率限制如何调整?
抖音抖店API请求获取宝贝详情数据的调用频率受限,需遵循平台规则。开发者可通过提升账号等级、申请更高配额、优化业务逻辑(如缓存数据、异步处理、批量请求)及监控调整等方式来应对。
|
存储 缓存 Java
Android性能优化:内存管理与LeakCanary技术详解
【7月更文挑战第21天】内存管理是Android性能优化的关键部分,而LeakCanary则是进行内存泄漏检测和修复的强大工具。
|
存储 安全 测试技术
渗透测试之白盒测试:一种深入的安全性评估方法
渗透测试中的白盒测试是一种利用系统详细信息(如源代码、数据库结构和网络拓扑)进行深度安全评估的方法。通过源代码审查、数据库分析和网络拓扑研究,测试人员能更准确地发现漏洞并提高测试效率。尽管白盒测试能深入揭露潜在威胁,但也面临信息获取难、代码理解复杂及对测试人员高技能要求的挑战。
渗透测试之白盒测试:一种深入的安全性评估方法