国网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来说,面更窄,资料也更少,如果产品化,有测试平台的话,还是不难实现的。

相关文章
|
安全 Java 编译器
kotlin面试题
kotlin面试题
1098 1
|
存储 开发者
国标GB28181协议客户端开发(二)程序架构和注册
国标GB28181协议客户端开发(二)程序架构和注册
1435 0
|
数据安全/隐私保护
如何在win10上连接苹果无线键盘
用电脑自带的步骤记录器记录的过程如下: Step 1: 用户在"添加设备 (按钮)"上左键单击; Step 2: 用户在"Apple Wireless Keyboard (文本)"上左键单击; Step 3: 用户在"下一步(N) (按钮)"上左键单击; Step 4: 用户在"或者,尝试在它上面输入 密码。
5067 0
|
Linux Android开发 编解码
VLC播放RTSP视频延迟问题
之前写过一篇关于在Linux平台上编译Android平台上VLC播放器源代码的文章,vlc这款播放器非常优秀而且是开源的,它的核心是开源视频编解码库ffmpeg。而且这款播放器还支持RTSP协议,这个主要是用开源的live555来实现的,live555这个库以后还需要认真研习。
6431 0
|
测试技术 编译器 vr&ar
CMake深度解析:掌握add_custom_command,精通Makefile生成规则(一)
CMake深度解析:掌握add_custom_command,精通Makefile生成规则
1365 1
|
存储 人工智能 自然语言处理
Elasticsearch Inference API增加对阿里云AI的支持
本文将介绍如何在 Elasticsearch 中设置和使用阿里云的文本生成、重排序、稀疏向量和稠密向量服务,提升搜索相关性。
623 14
Elasticsearch Inference API增加对阿里云AI的支持
|
人工智能 数据处理 C++
《跨越语言壁垒:Python 人工智能原型到 C++可执行程序的转型之路》
在AI领域,Python因简便和丰富的库被广泛用于原型开发,但在高性能需求下,C++更胜一筹。本文探讨了将Python原型转换为C++程序的方法,包括代码分析、选择转换工具、数据类型匹配、内存管理及优化策略,强调了两者互补的重要性,旨在提升AI应用的性能与可靠性。
322 7
|
计算机视觉
vs2019_qt6.2.4_dcmtk3.6.7_vtk9.2.2_itk5.3_opencv4.6.0编译记录
这篇文章记录了使用VS2019编译Qt6.2.4、DCMTK3.6.7、VTK9.2.2、ITK5.3和OpenCV4.6.0的过程,包括下载和编译步骤,并提供了遇到编译错误时的解决方案和参考链接。
486 0
vs2019_qt6.2.4_dcmtk3.6.7_vtk9.2.2_itk5.3_opencv4.6.0编译记录
|
Java Windows
JVM - 一篇带你解决 JConsole 无法本地连接解决方案
JVM - 一篇带你解决 JConsole 无法本地连接解决方案
2477 0
JVM - 一篇带你解决 JConsole 无法本地连接解决方案
|
编解码 前端开发 开发工具
国网B接口调阅实时视频(INVITE)接口描述和消息示例
前面三篇blog分别介绍国网B接口注册、资源上报和资源信息获取,今天过一下国网B接口调阅实时视频相关的接口描述和消息示例,做过GB28181设备接入的都知道,国网B接口调阅实时视频流程和GB28181的基本一致的,区别在于SDP的一些参数描述,有些差别,举个例子,调阅实时视频,GB28181的SDP里面“s=Play”,国网B接口SDP取值定义“s=-”,如果严格按照规范来,估计好多系统都没法正常接入。
782 0