如何实现Android平台GB28181前端设备接入
技術背景
在實現(xiàn)Android平臺GB28181前端設備接入之前,我們幾年前就有了非常成熟的RTMP推送、RTSP推送和輕量級RTSP服務等模塊,特別是RTMP推送,行業(yè)內應用非常廣泛,好多開發(fā)者可能會問,既然有了以上模塊,干嘛還要實現(xiàn)GB28181的前端接入呢?
首先,我們了解下GB/T28181:
國標GB/T28181協(xié)議全稱《安全防范視頻監(jiān)控聯(lián)網(wǎng)系統(tǒng)信息傳輸、交換、控制技術要求》,是一個定義視頻聯(lián)網(wǎng)傳輸和設備控制標準的白皮書,由公安部科技信息化局提出,該標準規(guī)定了城市監(jiān)控報警聯(lián)網(wǎng)系統(tǒng)中信息傳輸、交換、控制的互聯(lián)結構、通信協(xié)議結構,傳輸、交換、控制的基本要求和安全性要求,以及控制、傳輸流程和協(xié)議接口等技術要求。解決了視頻間互聯(lián)互通,數(shù)據(jù)共享,以及設備控制的問題,這個問題從頂層解決了視頻信息各自為戰(zhàn)的問題,打通了視頻聯(lián)網(wǎng)的信息孤島。
技術特點
GB28181協(xié)議實現(xiàn)分兩塊,一塊是信令部分,一塊是流媒體數(shù)據(jù)傳輸。GB28181相對RTMP,支持TCP和UDP模式,信令流負責session交互,數(shù)據(jù)流負責數(shù)據(jù)傳輸,適合標準協(xié)議規(guī)范的平臺級產(chǎn)品對接。
Android終端除支持常規(guī)的音視頻數(shù)據(jù)接入外,還可以支持Subscribe訂閱實時位置(MobilePosition)、實時目錄查詢等,支持標準28181服務對接。
此外,產(chǎn)品設計這塊,媒體流支持最新GB28181-2016的UDP和TCP被動模式,參數(shù)配置,支持注冊有效期、心跳間隔、心跳間隔次數(shù)、TCP/UDP信令設置,支持RTP Sender IP地址類型、RTP Socket本地端口、SSRC、RTP socket 發(fā)送Buffer大小、RTP時間戳時鐘頻率設置,支持注冊成功、注冊超時、INVIT、ACK、BYE狀態(tài)回調。
功能設計
Android端GB28181前端設備模塊,支持常規(guī)的視頻采集、編碼設定,功能設計如下:
- ?[本地預覽]支持本地前后置攝像頭預覽;
- ?[視頻格式]H.264/H.265(Android H.265硬編碼);
- ?[音頻格式]AAC;
- ?[音量調節(jié)]Android平臺采集端支持實時音量調節(jié);
- ?[H.264硬編碼]支持H.264特定機型硬編碼;
- ?[H.265硬編碼]支持H.265特定機型硬編碼;
- ?[軟硬編碼參數(shù)配置]支持gop間隔、幀率、bit-rate設置;
- ?[軟編碼參數(shù)配置]支持軟編碼profile、軟編碼速度、可變碼率設置;
- ?[橫豎屏推流]Android平臺支持支持橫屏、豎屏推流;
- ?[多分辨率支持]支持攝像頭或屏幕多種分辨率設置;
- ?[移動端推屏]Android平臺支持后臺service推送屏幕(推送屏幕需要5.0+版本);
- ?[模式支持]媒體流支持最新GB28181-2016的UDP和TCP被動模式;
- ?[參數(shù)設置]支持注冊有效期、心跳間隔、心跳間隔次數(shù)、TCP/UDP信令設置;
- ?[參數(shù)設置]支持RTP Sender IP地址類型、RTP Socket本地端口、SSRC、RTP socket 發(fā)送Buffer大小、RTP時間戳時鐘頻率設置;
- ?[狀態(tài)回調]支持注冊成功、注冊超時、INVIT、ACK、BYE狀態(tài)回調;
- ?[水印]支持文字水印、png水印;
- ?[鏡像]Android平臺支持前置攝像頭實時鏡像功能;
- ?[前后攝像頭實時切換]Android平臺支持采集過程中,前后攝像頭切換;
- ?[復雜網(wǎng)絡處理]支持斷網(wǎng)重連等各種網(wǎng)絡環(huán)境自動適配;
- ?[動態(tài)碼率]支持根據(jù)網(wǎng)絡情況自動調整推流碼率;
- ?[實時靜音]支持推送過程中,實時靜音/取消靜音;
- ?[實時快照]支持推流過程中,實時快照;
- ?[降噪]支持環(huán)境音、手機干擾等引起的噪音降噪處理、自動增益、VAD檢測;
- ?[外部編碼前視頻數(shù)據(jù)對接]支持YUV數(shù)據(jù)對接;
- ?[外部編碼前音頻數(shù)據(jù)對接]支持PCM對接;
- ?[外部編碼后視頻數(shù)據(jù)對接]支持外部H.264數(shù)據(jù)對接;
- ?[外部編碼后音頻數(shù)據(jù)對接]外部AAC數(shù)據(jù)對接;
- ?[擴展錄像功能]支持和錄像模塊組合使用,錄像相關功能;
- ?[服務器兼容]支持標準GB28181服務。
接口設計
接口設計,我們分兩塊:RTP Sender接口和GB28181接口;
RTP Sender接口描述:
1. 創(chuàng)建RTP Sender實例,返回實例句柄:
/** 創(chuàng)建RTP Sender實例** @param reserve:保留參數(shù)傳0** @return RTP Sender 句柄,0表示失敗*/public native long CreateRTPSender(int reserve);2.?設置 RTP Sender傳輸協(xié)議,0:UDP, 1:TCP, 默認是UDP
/***設置 RTP Sender傳輸協(xié)議** @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);4.?設置 RTP Sender RTP Socket本地端口,port, 必須是偶數(shù),設置0的話SDK會自動分配, 默認值是0
/***設置 RTP Sender RTP Socket本地端口** @param rtp_sender_handle, CreateRTPSender返回值* @param port, 必須是偶數(shù),設置0的話SDK會自動分配, 默認值是0** @return {0} if successful*/public native int SetRTPSenderLocalPort(long rtp_sender_handle, int port);5.?設置 RTP Sender SSRC
/***設置 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);6.?設置 RTP Sender RTP socket 發(fā)送Buffer大小
/***設置 RTP Sender RTP socket 發(fā)送Buffer大小** @param rtp_sender_handle, CreateRTPSender返回值* @param buffer_size, 必須大于0, 默認是512*1024, 當前僅對UDP socket有效, 根據(jù)視頻碼率考慮設置合適的值** @return {0} if successful*/public native int SetRTPSenderSocketSendBuffer(long rtp_sender_handle, int buffer_size);7.?設置 RTP Sender RTP時間戳時鐘頻率
/***設置 RTP Sender RTP時間戳時鐘頻率** @param rtp_sender_handle, CreateRTPSender返回值* @param clock_rate, 必須大于0, 對于GB28181 PS規(guī)定是90kHz, 也就是90000** @return {0} if successful*/public native int SetRTPSenderClockRate(long rtp_sender_handle, int clock_rate);8.?設置 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);9.?初始化RTP Sender, 初始化之前先調用上面的接口配置相關參數(shù)
/***初始化RTP Sender, 初始化之前先調用上面的接口配置相關參數(shù)** @param rtp_sender_handle, CreateRTPSender返回值** @return {0} if successful*/public native int InitRTPSender(long rtp_sender_handle);10.?獲取RTP Sender RTP Socket本地端口
/***獲取RTP Sender RTP Socket本地端口** @param rtp_sender_handle, CreateRTPSender返回值** @return 失敗返回0, 成功的話返回響應的端口, 請在InitRTPSender返回成功之后調用*/public native int GetRTPSenderLocalPort(long rtp_sender_handle);11.?UnInit RTP Sender
/*** UnInit RTP Sender** @param rtp_sender_handle, CreateRTPSender返回值** @return {0} if successful*/public native int UnInitRTPSender(long rtp_sender_handle);12.?釋放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, 協(xié)議定義是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. 相關參數(shù)初始化
/*** GB28181 相關參數(shù),可以修改相關參數(shù)后測試 ***/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 相關參數(shù),可以修改相關參數(shù)后測試 ***/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);// 目前測試下來,發(fā)送BYE之后,有些服務器會立即發(fā)送INVITE,是否發(fā)送BYE根據(jù)實際情況看gb28181_agent_.stop();btnGB28181Agent.setText("啟動GB28181");}else {if ( gb28181_agent_.start() ) {btnGB28181Agent.setText("停止GB28181");}}}}3. InitGB28181Agent實現(xiàn)
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_);// 可選參數(shù)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. 注冊成功后,返回注冊時間
@Overridepublic void ntsRegisterOK(String dateString) {Log.i(TAG, "ntsRegisterOK Date: " + (dateString!= null? dateString : ""));}5. 注冊超時回調
@Overridepublic void ntsRegisterTimeout() {Log.e(TAG, "ntsRegisterTimeout");}6. 注冊transport異?;卣{
@Overridepublic void ntsRegisterTransportError(String errorInfo) {Log.e(TAG, "ntsRegisterTransportError error:" + (errorInfo != null?errorInfo :""));}7. 心跳異?;卣{
@Overridepublic 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() {@Overridepublic 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后,創(chuàng)建RTP Sender,根據(jù)返回的信息,設定相關參數(shù)
@Overridepublic void ntsOnInvitePlay(String deviceId, InvitePlaySessionDescription session_des) {handler.postDelayed(new Runnable() {@Overridepublic 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());// 可以先給信令服務器發(fā)送臨時振鈴響應//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); // 設置到2MlibPublisher.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. 取消播放
@Overridepublic void ntsOnCancelPlay(String deviceId) {// 這里取消Play會話handler.postDelayed(new Runnable() {@Overridepublic 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收到后,開始發(fā)送音視頻數(shù)據(jù)
@Overridepublic void ntsOnAckPlay(String deviceId) {handler.postDelayed(new Runnable() {@Overridepublic 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異常處理
@Overridepublic void ntsOnPlayInviteResponseException(String deviceId, int statusCode, String errorInfo) {// 這里要釋放掉響應的資源Log.i(TAG, "ntsOnPlayInviteResponseException, deviceId=" + deviceId + " statusCode=" +statusCode+ " errorInfo:" + errorInfo);handler.postDelayed(new Runnable() {@Overridepublic 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停止發(fā)送數(shù)據(jù)
@Overridepublic void ntsOnByePlay(String deviceId){handler.postDelayed(new Runnable() {@Overridepublic 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終止處理
@Overridepublic void ntsOnPlayDialogTerminated(String deviceId) {/*Play會話對應的對話終止, 一般不會出發(fā)這個回調,目前只有在響應了200K, 但在64*T1時間后還沒收到ACK,才可能會出發(fā)收到這個請做相關清理處理*/handler.postDelayed(new Runnable() {@Overridepublic 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傳輸外,支持信令和數(shù)據(jù)傳輸分離,可實現(xiàn)其他終端針對前端設備的按需播放和處理,無需單獨的信令支撐。缺點是外部支持GB28181的服務器不多,開源如SRS服務器針對GB28181的支持暫不夠商用級,期待后續(xù)版本升級。
總結
以上是生活随笔為你收集整理的如何实现Android平台GB28181前端设备接入的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: NC代码调试 - 持续更新
- 下一篇: u盘修复系统