如何快速实现Android平台前端设备接入能力

简介: SIP(会话初始化协议)是在 IP网络上进行多媒体通信的应用层控制协议,以几种RFC的形式提供,其中最重要的是包含核心协议规范的RFC3261。该协议用于创建,修改和终止与一个或多个参与者的会话。通过会话,我们了解了一组进行通信的发送方和接收方,以及在通信过程中这些发送方和接收方保持的状态。会话的示例可以包括Internet电话呼叫,多媒体分发,多媒体会议,分布式计算机游戏等。

技术背景

SIP(会话初始化协议)是在 IP网络上进行多媒体通信的应用层控制协议,以几种RFC的形式提供,其中最重要的是包含核心协议规范的RFC3261。该协议用于创建,修改和终止与一个或多个参与者的会话。通过会话,我们了解了一组进行通信的发送方和接收方,以及在通信过程中这些发送方和接收方保持的状态。会话的示例可以包括Internet电话呼叫,多媒体分发,多媒体会议,分布式计算机游戏等。


SIP不是通信设备将需要的唯一协议,也不意味着是通用协议。SIP的目的仅仅是使通信成为可能,通信本身必须通过其他方式(可能还有其他协议)来实现。


与SIP一起最常使用的两种协议是RTP和SDP。 RTP协议用于承载实时多媒体数据(包括音频,视频和文本),该协议可以将数据编码和拆分为数据包,并通过Internet传输此类数据包。


另一个重要的协议是SDP,用于描述和编码会话参与者的功能。 然后,将这种描述用于协商会话的特征,以便所有设备都可以参与(例如,包括协商用于编码媒体的编解码器,以便所有参与者都可以对其进行解码,协商使用的传输协议 等等)。


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


该标准规定了城市监控报警联网系统中信息传输、交换、控制的互联结构、通信协议结构,传输、交换、控制的基本要求和安全性要求,以及控制、传输流程和协议接口等技术要求。该标准适用于安全防范监控报警联网系统的方案设计、系统检测、验收以及与之相关的设备研发、生产,其他信息系统可参考采用。

该标准于2012年6月1日正式发布实施,在全国范围内的平安城市项目建设中被普遍推广应用。GB/T28181-2011标准自发布以来,受到了各大视频监控厂商的积极响应。截止2012年底,有近百家视频监控企业通过公安部一所、公安部三所的认证,如深圳宙视达、浙江宇视、超视科技、东方网力、海康威视、高远时代、浙江大华、先进视讯、波粒科技、华为技术、中兴力维、中星电子、科达、天地伟业等。

相关接口

废话不多说,直接上设计接口,好多开发者网上看到的大多是非常简单的接口。极致简单,一直是我们追求的目标,但是更好的参数化配置和可扩展的设计,也是一个规范化产品的必经之路。


除了常规的音视频采集编码接口外,GB28181设备接入模块,主要分信令交互和媒体数据传输两个部分,以大牛直播SDK(官方)分别介绍下相关接口设计。

媒体数据传输相关

  /*+++++++++++++++RTP Sender相关接口+++++++++++++++*/
  /*
   * 创建RTP Sender实例
   *
   * @param reserve:保留参数传0
   *
   * @return RTP Sender 句柄,0表示失败
   */
  public native long CreateRTPSender(int reserve);
  /**
   *设置 RTP Sender传输协议
   *
   * @param rtp_sender_handle, CreateRTPSender返回值
   * @param transport_protocol, 0:UDP, 1:TCP, 默认是UDP
   *
   * @return {0} if successful
   */
  public native int SetRTPSenderTransportProtocol(long rtp_sender_handle, int transport_protocol);
  /**
   *设置 RTP Sender IP地址类型
   *
   * @param rtp_sender_handle, CreateRTPSender返回值
   * @param ip_address_type, 0:IPV4, 1:IPV6, 默认是IPV4, 当前仅支持IPV4
   *
   * @return {0} if successful
   */
  public native int SetRTPSenderIPAddressType(long rtp_sender_handle, int ip_address_type);
  /**
   *设置 RTP Sender RTP Socket本地端口
   *
   * @param rtp_sender_handle, CreateRTPSender返回值
   * @param port, 必须是偶数,设置0的话SDK会自动分配, 默认值是0
   *
   * @return {0} if successful
   */
  public native int SetRTPSenderLocalPort(long rtp_sender_handle, int port);
  /**
   *设置 RTP Sender SSRC
   *
   * @param rtp_sender_handle, CreateRTPSender返回值
   * @param ssrc, 如果设置的话,这个字符串要能转换成uint32类型, 否则设置失败
   *
   * @return {0} if successful
   */
  public native int SetRTPSenderSSRC(long rtp_sender_handle, String ssrc);
  /**
   *设置 RTP Sender RTP socket 发送Buffer大小
   *
   * @param rtp_sender_handle, CreateRTPSender返回值
   * @param buffer_size, 必须大于0, 默认是512*1024, 当前仅对UDP socket有效, 根据视频码率考虑设置合适的值
   *
   * @return {0} if successful
   */
  public native int SetRTPSenderSocketSendBuffer(long rtp_sender_handle, int buffer_size);
  /**
   *设置 RTP Sender RTP时间戳时钟频率
   *
   * @param rtp_sender_handle, CreateRTPSender返回值
   * @param clock_rate, 必须大于0, 对于GB28181 PS规定是90kHz, 也就是90000
   *
   * @return {0} if successful
   */
  public native int SetRTPSenderClockRate(long rtp_sender_handle, int clock_rate);
  /**
   *设置 RTP Sender 目的IP地址, 注意当前用在GB2818推送上,只设置一个地址,将来扩展如果用在其他地方,可能要设置多个目的地址,到时候接口可能会调整
   *
   * @param rtp_sender_handle, CreateRTPSender返回值
   * @param address, IP地址
   * @param port, 端口
   *
   * @return {0} if successful
   */
  public native int SetRTPSenderDestination(long rtp_sender_handle, String address, int port);
  /**
   *初始化RTP Sender, 初始化之前先调用上面的接口配置相关参数
   *
   * @param rtp_sender_handle, CreateRTPSender返回值
   *
   * @return {0} if successful
   */
  public native int InitRTPSender(long rtp_sender_handle);
  /**
   *获取RTP Sender RTP Socket本地端口
   *
   * @param rtp_sender_handle, CreateRTPSender返回值
   *
   * @return 失败返回0, 成功的话返回响应的端口, 请在InitRTPSender返回成功之后调用
   */
  public native int GetRTPSenderLocalPort(long rtp_sender_handle);
  /**
   * UnInit RTP Sender
   *
   * @param rtp_sender_handle, CreateRTPSender返回值
   *
   * @return {0} if successful
   */
  public native int UnInitRTPSender(long rtp_sender_handle);
  /**
   * 释放RTP Sender, 释放之后rtp_sender_handle就无效了,请不要再使用
   *
   * @param rtp_sender_handle, CreateRTPSender返回值
   *
   * @return {0} if successful
   */
  public native int DestoryRTPSender(long rtp_sender_handle);

信令相关操作

  /*+++++++++++++++GB28181相关接口+++++++++++++++*/
  /**
   * 设置GB28181 RTP Sender
   *
   * @param rtp_sender_handle, CreateRTPSender返回值
   * @param rtp_payload_type, 对于GB28181 PS, 协议定义是96, 具体以SDP为准
   *
   * @return {0} if successful
   */
  public native int SetGB28181RTPSender(long handle, long rtp_sender_handle, int rtp_payload_type);
  /**
   * 启动 GB28181 媒体流
   *
   * @return {0} if successful
   */
  public native int StartGB28181MediaStream(long handle);
  /**
   * 停止 GB28181 媒体流
   *
   * @return {0} if successful
   */
  public native int StopGB28181MediaStream(long handle);
  /*---------------GB28181相关接口---------------*/

启动、停止GB28181

完成视频分辨率等参数配置后,点击“启动GB28181”,即可开始走信令交互流程,设备端主动发送Register,进入后续交互流程。


如需停止GB28181,点停止即可。


GB28181设备端主要实现了按需推送数据到平台端。

5c82614fde9b40af911cf0bdeb424163.jpg

    class ButtonGB28181AgentListener implements OnClickListener {
        public void onClick(View v) {
            stopGB28181Stream();
            destoryRTPSender();
            if (null == gb28181_agent_ ) {
                if( !initGB28181Agent() )
                    return;
            }
            if (gb28181_agent_.isRunning()) {
                gb28181_agent_.terminateAllPlays(true);// 目前测试下来,发送BYE之后,有些服务器会立即发送INVITE,是否发送BYE根据实际情况看
                gb28181_agent_.stop();
                btnGB28181Agent.setText("启动GB28181");
            }
            else {
                if ( gb28181_agent_.start() ) {
                    btnGB28181Agent.setText("停止GB28181");
                }
            }
        }
    }

注册

当Andriod设备端第一次接入平台端时,设备端将持续向平台端发送 REGISTER消息,直到 Server端回复"200 OK"代表注册成功。


如果设备或系统注册不成功,宜延迟一定的随机时间后重新注册每隔一定时间客户端都会再次向服务器刷新注册,防止注册过期导致连接断开。


以基本注册为例:基本注册采用IETFRFC3261规定的基于数字摘要的挑战应答式安全技术进行注册。

0e3e4e974efe45ba983fea40e3e6131a.png

注册流程描述如下 :

a) 1:SIP 代理向 SIP 服务器发送 Register请求;

b) 2:SIP 服务器向 SIP代理发送响应401,并在响应的消息头 WWW_Authenticate字段中给出适合S IP 代理的认证体制和参数 ;

c) 3:SIP 代理重新向 SIP 服务器发送 Register 请求 , 在请求的 Authorization 字段给出信任书, 包含认证信息 ;

d) 4:SIP 服务器对请求进行验证 , 如果检查出 SIP 代理身份合法 , 向 SIP 代理发送成功响应 200OK, 如果身份不合法则发送拒绝服务应答 。

相关注册反馈:

    @Override
    public void ntsRegisterOK(String dateString) {
        Log.i(TAG, "ntsRegisterOK Date: " + (dateString!= null? dateString : ""));
    }
    @Override
    public void ntsRegisterTimeout() {
        Log.e(TAG, "ntsRegisterTimeout");
    }
    @Override
    public void ntsRegisterTransportError(String errorInfo) {
        Log.e(TAG, "ntsRegisterTransportError error:" + (errorInfo != null?errorInfo :""));
    }

信息查询(Catalog)

注册成功后,客户端与服务器之间信息查询操作,如目录查询、历史录像文件查看等,GB28181使用 SIP扩展协议规定的 Message方法实现。目前使用了两种类型的查询命令:Catalog 设备目录查询消息、RecordInfo 历史录像文件查询消息。

本文以Catalog设备目录查询消息为例:平台端向设备端发送Catalog请求,设备端回复200 OK后,发送设备信息,平台端回复200 OK,如遇多个设备信息,切记分次发送。

    private boolean initGB28181Agent()
    {
        if ( gb28181_agent_ != null )
            return  true;
        String local_ip_addr = IPAddrUtils.getIpAddress(myContext);
        Log.i(TAG, "initGB28181Agent local ip addr: " + local_ip_addr);
        if ( local_ip_addr == null || local_ip_addr.isEmpty() ) {
            Log.e(TAG, "initGB28181Agent local ip is empty");
            return  false;
        }
        gb28181_agent_ = GBSIPAgentFactory.getInstance().create();
        if ( gb28181_agent_ == null ) {
            Log.e(TAG, "initGB28181Agent create agent failed");
            return false;
        }
        gb28181_agent_.addListener(this);
        // 必填信息
        gb28181_agent_.setLocalAddressInfo(local_ip_addr, gb28181_sip_local_port_);
        gb28181_agent_.setServerParameter(gb28181_sip_server_addr_, gb28181_sip_server_port_, gb28181_sip_server_id_, gb28181_sip_server_domain_);
        gb28181_agent_.setUserInfo(gb28181_sip_username_, gb28181_sip_password_);
        // 可选参数
        gb28181_agent_.setUserAgent(gb28181_sip_user_agent_filed_);
        gb28181_agent_.setTransportProtocol(gb28181_sip_trans_protocol_==0?"UDP":"TCP");
        // GB28181配置
        gb28181_agent_.config(gb28181_reg_expired_, gb28181_heartbeat_interval_, gb28181_heartbeat_count_);
        com.gb28181.ntsignalling.Device gb_device = new com.gb28181.ntsignalling.Device("34020000001380000001", "安卓测试设备", Build.MANUFACTURER, Build.MODEL,
                    "宇宙","火星1","火星", true);
        getLocation(this);
        gb_device.setLongitude(mLongitude);
        gb_device.setLatitude(mLatitude);
        gb28181_agent_.addDevice(gb_device);
        if (!gb28181_agent_.initialize()) {
            gb28181_agent_ = null;
            Log.e(TAG, "initGB28181Agent gb28181_agent_.initialize failed.");
            return  false;
        }
        return true;
    }

心跳(KeepAlive)

KeepAlive以MESSAGE的形式传递,异常处理如下:

    @Override
    public void ntsOnHeartBeatException(int exceptionCount,  String lastExceptionInfo) {
        Log.e(TAG, "ntsOnHeartBeatException heart beat timeout count reached, count:" + exceptionCount+
                ", exception info:" + (lastExceptionInfo!=null?lastExceptionInfo:""));
        // 10毫秒后,停止信令, 然后重启
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                Log.i(TAG, "gb28281_heart_beart_timeout");
                stopGB28181Stream();
                destoryRTPSender();
                if (gb28181_agent_ != null) {
                    Log.i(TAG, "gb28281_heart_beart_timeout sip stop");
                    gb28181_agent_.stop();
                    Log.i(TAG, "gb28281_heart_beart_timeout sip start");
                    gb28181_agent_.start();
                }
            }
        },10);
    }

Invite处理

假定整个信令交互流程顺利,Android设备端完成Register、Catalog、KeepAlive消息处理后,平台端发过来Invite请求并携带SDP消息体。


Android设备端可获取到比如deviceid, tcp/udp传输模式、rtp端口,address类型等,并构建200 OK,携带相关的音视频信息。

    @Override
    public void ntsOnInvitePlay(String deviceId, InvitePlaySessionDescription session_des) {
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                Log.i(TAG,"ntsInviteReceived, device_id:" +device_id_+", is_tcp:" + session_des_.isRTPOverTCP()
                        + " rtp_port:" + session_des_.getMediaPort() + " ssrc:" + session_des_.getSSRC()
                        + " address_type:" + session_des_.getAddressType() + " address:" + session_des_.getAddress());
                // 可以先给信令服务器发送临时振铃响应
                //sip_stack_android.respondPlayInvite(180, device_id_);
                long rtp_sender_handle = libPublisher.CreateRTPSender(0);
                if ( rtp_sender_handle == 0 ) {
                    gb28181_agent_.respondPlayInvite(488, device_id_);
                    Log.i(TAG, "ntsInviteReceived CreateRTPSender failed, response 488, device_id:" + device_id_);
                    return;
                }
                gb28181_rtp_payload_type_ = session_des_.getPSRtpMapAttribute().getPayloadType();
                libPublisher.SetRTPSenderTransportProtocol(rtp_sender_handle, session_des_.isRTPOverUDP()?0:1);
                libPublisher.SetRTPSenderIPAddressType(rtp_sender_handle, session_des_.isIPv4()?0:1);
                libPublisher.SetRTPSenderLocalPort(rtp_sender_handle, 0);
                libPublisher.SetRTPSenderSSRC(rtp_sender_handle, session_des_.getSSRC());
                libPublisher.SetRTPSenderSocketSendBuffer(rtp_sender_handle, 2*1024*1024); // 设置到2M
                libPublisher.SetRTPSenderClockRate(rtp_sender_handle, session_des_.getPSRtpMapAttribute().getClockRate());
                libPublisher.SetRTPSenderDestination(rtp_sender_handle, session_des_.getAddress(), session_des_.getMediaPort());
                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(myContext);
                gb28181_agent_.respondPlayInviteOK(device_id_,local_ip_addr, local_port);
                gb28181_rtp_sender_handle_ = rtp_sender_handle;
            }
            private String device_id_;
            private InvitePlaySessionDescription session_des_;
            public Runnable set(String device_id, InvitePlaySessionDescription session_des) {
                this.device_id_ = device_id;
                this.session_des_ = session_des;
                return this;
            }
        }.set(deviceId, session_des),0);
    }

Ack确认

平台端发送Ack确认,设备端收到Ack后,开始发送音视频数据。

    @Override
    public void ntsOnAckPlay(String deviceId) {
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                Log.i(TAG,"ntsOnACKPlay, device_id:" +device_id_);
                if (!isRecording && !isRTSPPublisherRunning && !isPushingRtsp && !isPushingRtmp) {
                    InitAndSetConfig();
                }
                libPublisher.SetGB28181RTPSender(publisherHandle, gb28181_rtp_sender_handle_, gb28181_rtp_payload_type_);
                int startRet = libPublisher.StartGB28181MediaStream(publisherHandle);
                if (startRet != 0) {
                    if (!isRecording && !isRTSPPublisherRunning && !isPushingRtmp && !isPushingRtsp) {
                        if (publisherHandle != 0) {
                            libPublisher.SmartPublisherClose(publisherHandle);
                            publisherHandle = 0;
                        }
                    }
                    destoryRTPSender();
                    Log.e(TAG, "Failed to start GB28181 service..");
                    return;
                }
                if (!isRecording && !isRTSPPublisherRunning && !isPushingRtsp && !isPushingRtmp) {
                    if (pushType == 0 || pushType == 1) {
                        CheckInitAudioRecorder();    //enable pure video publisher..
                    }
                }
                isGB28181StreamRunning = true;
            }
            private String device_id_;
            public Runnable set(String device_id) {
                this.device_id_ = device_id;
                return this;
            }
        }.set(deviceId),0);
    }

BYE

如果客户端需要断开Invite会话,则会发送BYE,客户端返回200 OK。

    @Override
    public void ntsOnByePlay(String deviceId)
    {
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                Log.i(TAG, "ntsOnByePlay, stop GB28181 media stream, deviceId=" + device_id_);
                stopGB28181Stream();
                destoryRTPSender();
            }
            private String device_id_;
            public Runnable set(String device_id) {
                this.device_id_ = device_id;
                return this;
            }
        }.set(deviceId),0);
    }

注销

客户端向服务器发送 Register命令消息,消息中的 Expire字段设置为0即是注销。

相关文章
|
9天前
|
安全 Android开发 iOS开发
探索安卓与iOS开发的差异:平台特性与用户体验的深度对比
在移动应用开发的广阔天地中,安卓和iOS两大平台各占半壁江山。本文旨在通过数据驱动的分析方法,深入探讨这两大操作系统在开发环境、用户界面设计及市场表现等方面的差异。引用最新的行业报告和科研数据,结合技术专家的观点,本文将提供对开发者和市场分析师均有价值的洞见。
|
12天前
|
Java 开发工具 Android开发
探索Android与iOS开发的差异:平台选择对项目成功的影响
在移动应用开发的广阔天地中,Android和iOS两大平台各自占据着半壁江山。本文将深入探讨这两个平台在开发过程中的关键差异点,包括编程语言、开发工具、用户界面设计、性能优化以及市场覆盖等方面。通过对这些关键因素的比较分析,旨在为开发者提供一个清晰的指南,帮助他们根据项目需求和目标受众做出明智的平台选择。
|
11天前
|
监控 Android开发 iOS开发
探索Android与iOS开发的差异:平台、工具和用户体验的比较
【6月更文挑战第25天】在移动应用开发的广阔天地中,Android和iOS两大平台各领风骚,它们在开发环境、工具选择及用户体验设计上展现出独特的风貌。本文将深入探讨这两个操作系统在技术实现、市场定位和用户交互方面的关键差异,旨在为开发者提供一个全景式的视图,帮助他们在面对项目决策时能够更加明智地选择适合自己项目需求的平台。
|
18天前
|
Java Android开发 Swift
探索Android与iOS开发的差异:平台选择对项目成功的影响
【6月更文挑战第18天】在移动应用开发的广阔天地中,Android和iOS两大平台各据一方,它们在市场份额、用户群体及开发环境上各有千秋。本文将深入分析这两个操作系统的开发差异,探讨如何根据项目需求选择合适的平台,并讨论跨平台解决方案的可行性与挑战。我们将通过实际案例,揭示平台选择对项目成功的关键性影响,为开发者提供决策支持。
|
19天前
|
安全 IDE Android开发
探索Android与iOS开发的差异:平台特性与编程实践
【6月更文挑战第17天】在移动应用开发的广阔天地中,Android和iOS两大平台各自占据半壁江山。它们在用户群体、系统架构以及开发环境上的差异,为开发者带来了不同的挑战和机遇。本文深入探讨了这两个平台在技术实现、界面设计、性能优化等方面的主要区别,并提供了实用的开发建议,旨在帮助开发者更好地理解各自平台的特性,从而创造出更加优秀的移动应用。
|
22天前
|
安全 Android开发 iOS开发
探索Android与iOS开发的差异:平台特性与用户体验的对比分析
在移动应用开发的广阔天地中,Android和iOS两大阵营各据一方。本文将深入探讨这两个操作系统在开发环境、编程语言、用户界面设计及市场分布等方面的主要区别。通过比较分析,我们将揭示各自平台的特有优势,并讨论如何根据目标受众和业务需求选择适合的开发平台。
|
24天前
|
安全 Android开发 iOS开发
探索Android与iOS开发平台的差异
【6月更文挑战第12天】在移动应用开发的广阔天地中,Android和iOS两大阵营各具特色,它们之间的差异不仅体现在技术层面,更影响着开发者的选择和用户的体验。本文将深入探讨这两大平台在开发环境、用户界面设计、市场策略及安全性方面的主要区别,为开发者提供一份实用的指南,帮助他们在这两个不同的世界中做出明智的决策。
20 3
|
3天前
|
Java Android开发 iOS开发
探索安卓与iOS开发的差异:平台特性与创新潜力
在移动应用开发的广阔天地中,安卓和iOS两大平台各占据一方。本文深入剖析了这两个操作系统的开发环境、工具、语言及市场趋势,旨在为开发者提供一个全面的比较视角。文章将基于最新的行业报告、技术论坛讨论以及专家分析,详细阐述两个平台的技术架构差异、开发成本和用户体验设计的不同点。通过数据支持的论证,揭示安卓与iOS在创新潜力上的独特优势,并探讨它们如何塑造未来的移动应用生态。
5 0
|
1月前
|
Java Android开发 Swift
探索安卓与iOS开发的差异:平台选择对项目成功的影响
在移动应用开发的浩瀚宇宙中,安卓和iOS这两大星系以其独特的引力影响着开发者的航向。本文将穿梭于这两个平台的开发环境、编程语言、用户界面设计、性能优化以及市场分布等方面,揭示它们之间的核心差异。我们将通过比较分析,为开发者提供决策时的参考坐标,帮助他们根据项目需求和目标受众选择合适的平台。加入我们,一起揭开安卓与iOS开发的神秘面纱,探索它们如何塑造移动应用的未来。
|
29天前
|
安全 Java Android开发
探索Android与iOS开发平台的差异与优势
【6月更文挑战第7天】在移动应用开发的广阔天地中,Android和iOS两大平台如同双子星般熠熠生辉。本文将深入探讨这两个系统在开发环境、用户界面设计、市场策略及安全性方面的不同之处,旨在为开发者提供一个清晰的指南,帮助他们根据项目需求和目标受众选择最合适的平台。