技术背景
我们在设计Android平台GB28181设备接入模块的时候,有这样的场景诉求,一个设备可能需要多个通道,常见的场景,比如车载终端,一台设备,可能需要接入多个摄像头,那么这台车载终端设备可以作为主设备,然后,主设备下,配置多个通道,听起来是不是有点儿类似于DVR或NVR?
技术实现
这里,我们说下,我们当时做这块,是怎么设计的,首先,在InitGB28181Agent的时候,添加设备通道,具体代码如下:
privatebooleaninitGB28181Agent() { if ( gb28181_agent_!=null ) returntrue; getLocation(context_); Stringlocal_ip_addr=IPAddrUtils.getIpAddress(context_); Log.i(TAG, "[daniusdk.com]initGB28181Agent local ip addr: "+local_ip_addr); if ( local_ip_addr==null||local_ip_addr.isEmpty() ) { Log.e(TAG, "[daniusdk.com]initGB28181Agent local ip is empty"); returnfalse; } gb28181_agent_=GBSIPAgentFactory.getInstance().create(); if ( gb28181_agent_==null ) { Log.e(TAG, "[daniusdk.com]initGB28181Agent create agent failed"); returnfalse; } gb28181_agent_.addListener(this); gb28181_agent_.addPlayListener(this); gb28181_agent_.addAudioBroadcastListener(this); gb28181_agent_.addDeviceControlListener(this); gb28181_agent_.addQueryCommandListener(this); // 必填信息gb28181_agent_.setLocalAddress(local_ip_addr); gb28181_agent_.setServerParameter(gb28181_sip_server_addr_, gb28181_sip_server_port_, gb28181_sip_server_id_, gb28181_sip_domain_); gb28181_agent_.setUserInfo(gb28181_sip_username_, gb28181_sip_password_); //gb28181_agent_.setUserInfo(gb28181_sip_username_, 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.gb.ntsignalling.Devicegb_device=newcom.gb.ntsignalling.Device("34020000001380000001", "安卓测试设备", Build.MANUFACTURER, Build.MODEL, "宇宙","火星1","火星", true); if (mLongitude!=null&&mLatitude!=null) { com.gb.ntsignalling.DevicePositiondevice_pos=newcom.gb.ntsignalling.DevicePosition(); device_pos.setTime(mLocationTime); device_pos.setLongitude(mLongitude); device_pos.setLatitude(mLatitude); gb_device.setPosition(device_pos); gb_device.setSupportMobilePosition(true); // 设置支持移动位置上报 } gb28181_agent_.addDevice(gb_device); com.gb28181.ntsignalling.Devicegb_device1=newcom.gb28181.ntsignalling.Device("34020000001380000002", "安卓测试设备2", Build.MANUFACTURER, Build.MODEL, "宇宙","火星1","火星", true); if (mLongitude!=null&&mLatitude!=null) { com.gb28181.ntsignalling.DevicePositiondevice_pos=newcom.gb28181.ntsignalling.DevicePosition(); device_pos.setTime(mLocationTime); device_pos.setLongitude(mLongitude); device_pos.setLatitude(mLatitude); gb_device1.setPosition(device_pos); gb_device1.setSupportMobilePosition(true); } gb28181_agent_.addDevice(gb_device1); if (!gb28181_agent_.createSipStack()) { gb28181_agent_=null; Log.e(TAG, "[daniusdk.com]initGB28181Agent gb28181_agent_.createSipStack failed."); returnfalse; } booleanis_bind_local_port_ok=false; // 最多尝试5000个端口inttry_end_port=gb28181_sip_local_port_base_+5000; try_end_port=try_end_port>65536?65536: try_end_port; for (inti=gb28181_sip_local_port_base_; i<try_end_port; ++i) { if (gb28181_agent_.bindLocalPort(i)) { is_bind_local_port_ok=true; break; } } if (!is_bind_local_port_ok) { gb28181_agent_.releaseSipStack(); gb28181_agent_=null; Log.e(TAG, "[daniusdk.com]initGB28181Agent gb28181_agent_.bindLocalPort failed."); returnfalse; } if (!gb28181_agent_.initialize()) { gb28181_agent_.unBindLocalPort(); gb28181_agent_.releaseSipStack(); gb28181_agent_=null; Log.e(TAG, "[daniusdk.com]initGB28181Agent gb28181_agent_.initialize failed."); returnfalse; } returntrue; }
可以看到,我们调用addDevice(gb_device);添加了两个通道,具体如下:
com.gb.ntsignalling.Devicegb_device=newcom.gb.ntsignalling.Device("34020000001380000001", "安卓测试设备", Build.MANUFACTURER, Build.MODEL, "宇宙","火星1","火星", true); if (mLongitude!=null&&mLatitude!=null) { com.gb.ntsignalling.DevicePositiondevice_pos=newcom.gb.ntsignalling.DevicePosition(); device_pos.setTime(mLocationTime); device_pos.setLongitude(mLongitude); device_pos.setLatitude(mLatitude); gb_device.setPosition(device_pos); gb_device.setSupportMobilePosition(true); // 设置支持移动位置上报} gb28181_agent_.addDevice(gb_device); com.gb28181.ntsignalling.Devicegb_device1=newcom.gb28181.ntsignalling.Device("34020000001380000002", "安卓测试设备2", Build.MANUFACTURER, Build.MODEL, "宇宙","火星1","火星", true); if (mLongitude!=null&&mLatitude!=null) { com.gb28181.ntsignalling.DevicePositiondevice_pos=newcom.gb28181.ntsignalling.DevicePosition(); device_pos.setTime(mLocationTime); device_pos.setLongitude(mLongitude); device_pos.setLatitude(mLatitude); gb_device1.setPosition(device_pos); gb_device1.setSupportMobilePosition(true); } gb28181_agent_.addDevice(gb_device1);
一台设备,分别对应了两个device。
invite请求过来的时候,我们会把deviceid回上来,上层可以针对不同的deviceid做预览处理:
publicvoidntsOnInvitePlay(StringdeviceId, SessionDescriptionsession_des) { handler_.postDelayed(newRunnable() { publicvoidrun() { // 先振铃响应下gb28181_agent_.respondPlayInvite(180, device_id_); MediaSessionDescriptionvideo_des=null; SDPRtpMapAttributeps_rtpmap_attr=null; // 28181 视频使用PS打包Vector<MediaSessionDescription>video_des_list=session_des_.getVideoPSDescriptions(); if (video_des_list!=null&&!video_des_list.isEmpty()) { for(MediaSessionDescriptionm : 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; } if (null==ps_rtpmap_attr) { gb28181_agent_.respondPlayInvite(488, device_id_); Log.i(TAG, "ntsOnInvitePlay get ps rtp map attribute 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() +" ss rc:"+video_des.getSS-RC() +" address_type:"+video_des.getAddressType() +" address:"+video_des.getAddress()); longrtp_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_=ps_rtpmap_attr.getPayloadType(); gb28181_rtp_encoding_name_=ps_rtpmap_attr.getEncodingName(); 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.SetRTPSenderSS-RC(rtp_sender_handle, video_des.getSS-RC()); libPublisher.SetRTPSenderSocketSendBuffer(rtp_sender_handle, 2*1024*1024); // 设置到2MlibPublisher.SetRTPSenderClockRate(rtp_sender_handle, 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; } intlocal_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); Stringlocal_ip_addr=IPAddrUtils.getIpAddress(context_); MediaSessionDescriptionlocal_video_des=newMediaSessionDescription(video_des.getType()); local_video_des.addFormat(String.valueOf(ps_rtpmap_attr.getPayloadType())); 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.setSS-RC(video_des.getSS-RC()); 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; } privateStringdevice_id_; privateSessionDescriptionsession_des_; publicRunnableset(Stringdevice_id, SessionDescriptionsession_des) { this.device_id_=device_id; this.session_des_=session_des; returnthis; } }.set(deviceId, session_des),0); }
收到ACK后,也携带了deviceid作为区分:
publicvoidntsOnAckPlay(StringdeviceId) { handler_.postDelayed(newRunnable() { publicvoidrun() { 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_); //libPublisher.SetGBTCPConnectTimeout(publisherHandle, 10*60*1000);//libPublisher.SetGBInitialTCPReconnectInterval(publisherHandle, 1000);//libPublisher.SetGBInitialTCPMaxReconnectAttempts(publisherHandle, 3);intstartRet=libPublisher.StartGB28181MediaStream(publisherHandle); if (startRet!=0) { if (!isRTSPPublisherRunning&&!isPushingRtmp&&!isRecording) { if (publisherHandle!=0) { longhandle=publisherHandle; publisherHandle=0; libPublisher.SmartPublisherClose(handle); } } destoryRTPSender(); Log.e(TAG, "Failed to start GB28181 service.."); return; } if (!isRTSPPublisherRunning&&!isPushingRtmp&&!isRecording) { CheckInitAudioRecorder(); } startLayerPostThread(); isGB28181StreamRunning=true; } privateStringdevice_id_; publicRunnableset(Stringdevice_id) { this.device_id_=device_id; returnthis; } }.set(deviceId),0); }
TerminatePlay处理如下:
publicvoidntsOnTerminatePlay(StringdeviceId) { handler_.postDelayed(newRunnable() { publicvoidrun() { Log.i(TAG, "ntsOnTerminatePlay, stop GB28181 media stream, deviceId="+device_id_); stopGB28181Stream(); destoryRTPSender(); } privateStringdevice_id_; publicRunnableset(Stringdevice_id) { this.device_id_=device_id; returnthis; } }.set(deviceId),0); }
这样每个deviceid对应一个实例,如果有多个摄像头的话,摄像头,可以按照deviceid来做区分,国标平台侧请求哪个deviceid的话,就启动这个deviceid对应的camera采集、编码和数据打包上传。