如何实现Android平台GB28181前端设备接入

631 阅读12分钟

技术背景

在实现Android平台GB28181前端设备接入之前,我们几年前就有了非常成熟的RTMP推送、RTSP推送和轻量级RTSP服务等模块,特别是RTMP推送,行业内应用非常广泛,好多开发者可能会问,既然有了以上模块,干嘛还要实现GB28181的前端接入呢?

首先,我们了解下GB/T28181:

国标GB/T28181协议全称《安全防范视频监控联网系统信息传输、交换、控制技术要求》,是一个定义视频联网传输和设备控制标准的白皮书,由公安部科技信息化局提出,该标准规定了城市监控报警联网系统中信息传输、交换、控制的互联结构、通信协议结构,传输、交换、控制的基本要求和安全性要求,以及控制、传输流程和协议接口等技术要求。解决了视频间互联互通,数据共享,以及设备控制的问题,这个问题从顶层解决了视频信息各自为战的问题,打通了视频联网的信息孤岛。

技术特点

GB28181协议实现分两块,一块是信令部分,一块是流媒体数据传输。GB28181相对RTMP,支持TCP和UDP模式,信令流负责session交互,数据流负责数据传输,适合标准协议规范的平台级产品对接。

Android终端除支持常规的音视频数据接入外,还可以支持Subscribe订阅实时位置(MobilePosition)、实时目录查询等,支持标准28181服务对接。

此外,产品设计这块,媒体流支持最新GB28181-2016的UDP和TCP被动模式,参数配置,支持注册有效期、心跳间隔、心跳间隔次数、TCP/UDP信令设置,支持RTP Sender IP地址类型、RTP Socket本地端口、SS-R-C、RTP socket 发送Buffer大小、RTP时间戳时钟频率设置,支持注册成功、注册超时、INVIT、ACK、BYE状态回调。

功能设计

Android端GB28181前端设备模块,支持常规的视频采集、编码设定,功能设计如下:

  • [本地预览]支持本地前后置摄像头预览;

  • [视频格式]H.264/H.265(Android H.265硬编码);

  • [音频格式]AAC;

  • [音量调节]Android平台采集端支持实时音量调节;

  • [H.264硬编码]支持H.264特定机型硬编码;

  • [H.265硬编码]支持H.265特定机型硬编码;

  • [软硬编码参数配置]支持gop间隔、帧率、bit-rate设置;

  • [软编码参数配置]支持软编码profile、软编码速度、可变码率设置;

  • [横竖屏推流]Android平台支持支持横屏、竖屏推流;

  • [多分辨率支持]支持摄像头或屏幕多种分辨率设置;

  • [移动端推屏]Android平台支持后台service推送屏幕(推送屏幕需要5.0+版本);

  • [模式支持]媒体流支持最新GB28181-2016的UDP和TCP被动模式;

  • [参数设置]支持注册有效期、心跳间隔、心跳间隔次数、TCP/UDP信令设置;

  • [参数设置]支持RTP Sender IP地址类型、RTP Socket本地端口、SS-RC、RTP socket 发送Buffer大小、RTP时间戳时钟频率设置;

  • [状态回调]支持注册成功、注册超时、INVIT、ACK、BYE状态回调;

  • [水印]支持文字水印、png水印;

  • [镜像]Android平台支持前置摄像头实时镜像功能;

  • [前后摄像头实时切换]Android平台支持采集过程中,前后摄像头切换;

  • [复杂网络处理]支持断网重连等各种网络环境自动适配;

  • [动态码率]支持根据网络情况自动调整推流码率;

  • [实时静音]支持推送过程中,实时静音/取消静音;

  • [实时快照]支持推流过程中,实时快照;

  • [降噪]支持环境音、手机干扰等引起的噪音降噪处理、自动增益、VAD检测;

  • [外部编码前视频数据对接]支持YUV数据对接;

  • [外部编码前音频数据对接]支持PCM对接;

  • [外部编码后视频数据对接]支持外部H.264数据对接;

  • [外部编码后音频数据对接]外部AAC数据对接;

  • [扩展录像功能]支持和录像模块组合使用,录像相关功能;

  • [服务器兼容]支持标准GB28181服务。

接口设计

接口设计,我们分两块:RTP Sender接口和GB28181接口,相关demo可以参看官网介绍;

RTP Sender接口描述:

1. 创建RTP Sender实例,返回实例句柄:

/\*  
   \* 创建RTP Sender实例  
   \*  
   \* @param reserve:保留参数传0  
   \*  
   \* @return RTP Sender 句柄,0表示失败  
   \*/  
  public native long CreateRTPSender(int reserve);
  1. 设置 RTP Sender传输协议,0:UDP, 1:TCP, 默认是UDP
/\*\*  
   \*设置 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);

3. 设置RTP Sender IP地址类型,如IPv4和IPv6,当前仅支持IPv4

/\*\*  
   \*设置 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);
  1. 设置 RTP Sender RTP Socket本地端口,port, 必须是偶数,设置0的话SDK会自动分配, 默认值是0
/\*\*  
   \*设置 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);
  1. 设置 RTP Sender SS-RC
/\*\*  
   \*设置 RTP Sender SS-RC  
   \*  
   \* @param rtp\_sender\_handle, CreateRTPSender返回值  
   \* @param ssrc, 如果设置的话,这个字符串要能转换成uint32类型, 否则设置失败  
   \*  
   \* @return {0} if successful  
   \*/  
  public native int SetRTPSenderSS\-RC(long rtp\_sender\_handle, String ssrc);
  1. 设置 RTP Sender RTP socket 发送Buffer大小
/\*\*  
   \*设置 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);
  1. 设置 RTP Sender RTP时间戳时钟频率
/\*\*  
   \*设置 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);
  1. 设置 RTP Sender 目的IP地址, 注意当前用在GB2818推送上,只设置一个地址,将来扩展如果用在其他地方,可能要设置多个目的地址,到时候接口可能会调整
/\*\*  
   \*设置 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);
  1. 初始化RTP Sender, 初始化之前先调用上面的接口配置相关参数
/\*\*  
   \*初始化RTP Sender, 初始化之前先调用上面的接口配置相关参数  
   \*  
   \* @param rtp\_sender\_handle, CreateRTPSender返回值  
   \*  
   \* @return {0} if successful  
   \*/  
  public native int InitRTPSender(long rtp\_sender\_handle);
  1. 获取RTP Sender RTP Socket本地端口
/\*\*  
   \*获取RTP Sender RTP Socket本地端口  
   \*  
   \* @param rtp\_sender\_handle, CreateRTPSender返回值  
   \*  
   \* @return 失败返回0, 成功的话返回响应的端口, 请在InitRTPSender返回成功之后调用  
   \*/  
  public native int GetRTPSenderLocalPort(long rtp\_sender\_handle);
  1. UnInit RTP Sender
/\*\*  
   \* UnInit RTP Sender  
   \*  
   \* @param rtp\_sender\_handle, CreateRTPSender返回值  
   \*  
   \* @return {0} if successful  
   \*/  
  public native int UnInitRTPSender(long rtp\_sender\_handle);
  1. 释放RTP Sender, 释放之后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相关接口

  1. 设置GB28181 RTP Sender
/\*\*  
   \* 设置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);

2. 启动 GB28181 媒体流

/\*\*  
   \* 启动 GB28181 媒体流  
   \*  
   \* @return {0} if successful  
   \*/  
  public native int StartGB28181MediaStream(long handle);

3. 停止 GB28181 媒体流

/\*\*  
   \* 停止 GB28181 媒体流  
   \*  
   \* @return {0} if successful  
   \*/  
  public native int StopGB28181MediaStream(long handle);

接口调用实例

1. 相关参数初始化

/\*\*\* GB28181 相关参数,可以修改相关参数后测试 \*\*\*/  
    GBSIPAgent     gb28181\_agent\_             \= null;  
    private int    gb28181\_sip\_local\_port\_    \= 12070;  
    private String gb28181\_sip\_server\_id\_     \= "34020000002000000001";  
    private String gb28181\_sip\_server\_domain\_ \= "3402000000";  
    private String gb28181\_sip\_server\_addr\_   \= "192.168.0.105";  
    private int    gb28181\_sip\_server\_port\_   \= 15060;  
  
    private String gb28181\_sip\_user\_agent\_filed\_  \= "NT GB28181 User Agent V1.0";  
    private String gb28181\_sip\_username\_   \= "31011500991320000069";  
    private String gb28181\_sip\_password\_   \= "12345678";  
  
    private int gb28181\_reg\_expired\_           \= 3600; // 注册有效期时间最小3600秒  
    private int gb28181\_heartbeat\_interval\_    \= 20; // 心跳间隔GB28181默认是60, 目前调整到20秒  
    private int gb28181\_heartbeat\_count\_       \= 3; // 心跳间隔3次失败,表示和服务器断开了  
    private int gb28181\_sip\_trans\_protocol\_    \= 0; // 0表示信令用UDP传输, 1表示信令用TCP传输  
  
    private long gb28181\_rtp\_sender\_handle\_ \= 0;  
    private int  gb28181\_rtp\_payload\_type\_  \= 96;  
  
    /\*\*\* GB28181 相关参数,可以修改相关参数后测试 \*\*\*/

2. 启动或停止GB28181操作

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");  
                }  
            }  
        }  
    }

3. InitGB28181Agent实现

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;  
    }

4. 注册成功后,返回注册时间

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

5. 注册超时回调

@Override  
    public void ntsRegisterTimeout() {  
        Log.e(TAG, "ntsRegisterTimeout");  
    }

6. 注册transport异常回调

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

7. 心跳异常回调

@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);  
    }

8. Invite返回OK后,创建RTP Sender,根据返回的信息,设定相关参数

@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\_.getSS\-RC()  
                        + " 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.SetRTPSenderSS\-RC(rtp\_sender\_handle, session\_des\_.getSS\-RC());  
                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);  
    }

9. 取消播放

@Override  
    public void ntsOnCancelPlay(String deviceId) {  
        // 这里取消Play会话  
        handler.postDelayed(new Runnable() {  
            @Override  
            public void run() {  
                Log.i(TAG, "ntsOnCancelPlay, deviceId=" + device\_id\_);  
  
                destoryRTPSender();  
            }  
  
            private String device\_id\_;  
  
            public Runnable set(String device\_id) {  
                this.device\_id\_ \= device\_id;  
                return this;  
            }  
  
        }.set(deviceId),0);  
    }

10. 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);  
    }

11. Invite异常处理

@Override  
    public void ntsOnPlayInviteResponseException(String deviceId, int statusCode, String errorInfo) {  
        // 这里要释放掉响应的资源  
        Log.i(TAG, "ntsOnPlayInviteResponseException, deviceId=" + deviceId + " statusCode=" +statusCode  
        + " errorInfo:" + errorInfo);  
  
        handler.postDelayed(new Runnable() {  
            @Override  
            public void run() {  
                Log.i(TAG, "ntsOnPlayInviteResponseException, deviceId=" + device\_id\_);  
  
                destoryRTPSender();  
            }  
  
            private String device\_id\_;  
  
            public Runnable set(String device\_id) {  
                this.device\_id\_ \= device\_id;  
                return this;  
            }  
  
        }.set(deviceId),0);  
    }

12. 收到Bye停止发送数据

@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);  
    }

13. Play Dialog终止处理

@Override  
    public void ntsOnPlayDialogTerminated(String deviceId) {  
        /\*  
        Play会话对应的对话终止, 一般不会出发这个回调,目前只有在响应了200K, 但在64\*T1时间后还没收到ACK,才可能会出发  
        收到这个请做相关清理处理  
        \*/  
        handler.postDelayed(new Runnable() {  
            @Override  
            public void run() {  
                Log.i(TAG, "ntsOnPlayDialogTerminated, 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);  
    }

总结

GB28181设计,除了支持TCP和UDP传输外,支持信令和数据传输分离,可实现其他终端针对前端设备的按需播放和处理,无需单独的信令支撑。缺点是外部支持GB28181的服务器不多,开源如SRS服务器针对GB28181的支持暂不够商用级,期待后续版本升级。