心跳文本


//摄像头发送过来的Keepalive保活信息
MESSAGE sip:34020000002000000001@192.168.1.201:5060 SIP/2.0 //MESSAGE 方法名,类似http的get/post方法
Via: SIP/2.0/UDP 192.168.1.8:5060;rport;branch=z9hG4bK700933079 //这个不用理会,但需要拷贝该字段,回复200要用
From: <sip:34020000001110000003@192.168.1.8:5060>;tag=1896094222 //同上
To: <sip:34020000002000000001@192.168.1.201:5060> //同上
Call-ID: 78119256@192.168.1.8 //同上
CSeq: 20 MESSAGE //唯一标识
Max-Forwards: 70
User-Agent: Dahua SIP UAS V1.0
Content-Type: Application/MANSCDP+xml //消息体格式
Content-Length:   178 //消息体长度,不带消息头,下面会空一行才会读到消息体,解析时注意下读到空行<?xml version="1.0" encoding="GB2312" ?> //编码
<Notify><CmdType>Keepalive</CmdType> //注意这里的消息体,大华发送的消息体有空格的,海康的消息体不带空格,需要做下处理,或者使用xml工具类,这里为了方便直接解析字符串<SN>0</SN><DeviceID>34020000001110000003</DeviceID> //设备编号<Status>OK</Status>
</Notify>//服务器需要对其进行200回复,超过不回复次数就会断流
SIP/2.0 200 OK //回复200
CSeq: 20 MESSAGE //拷贝上面心跳信息
Call-ID: 78119256@192.168.1.8 //同上
From: <sip:34020000001110000003@192.168.1.8:5060>;tag=1896094222 //同上
To: <sip:34020000002000000001@192.168.1.201:5060> //同上
Via: SIP/2.0/UDP 192.168.1.8:5060;rport;branch=z9hG4bK700933079 //同上
Content-Length: 0 //没有消息体,直接写0

心跳保活回复模板

//摄像头心跳保活回复
private static final String str_Keepalive_ok="SIP/2.0 200 OK"+n+"CSeq: {CSeq}"+n+"Call-ID: {Call-ID}"+n+"From: {From}"+n+"To: {To}"+n+"Via: {Via}"+n+"Content-Length: 0"+n+n;

心跳回复代码实现

//实现很简单,根据模板填充信息就行
//心跳和获取设备信息都是这个方法 获取设备信息自行抓包
if("MESSAGE".equals(map.get("method"))){//心跳回复200,不回复会导致断流if("Keepalive".equals(map.get("CmdType"))){sendStr=str_Keepalive_ok;sendStr=sendStr.replace("{CSeq}",map.get("CSeq"));sendStr=sendStr.replace("{Call-ID}",map.get("Call-ID"));sendStr=sendStr.replace("{From}",map.get("From"));sendStr=sendStr.replace("{To}",map.get("To"));sendStr=sendStr.replace("{Via}",map.get("Via"));//获取ssrc更新,理论上ssrc不会变的,这里保险,每次都更新最新的if(deviceInfo.getSsrc()==null){String deviceId=map.get("deviceId");deviceInfo.setSsrc(Utils.getSsrc("010000"+deviceId.substring(deviceId.length()-4)));}/*if(dataDeviceInfo.get(map.get("deviceId"))==null){System.out.println("restart");Map<String,String> d=new HashMap<>(2);String via=map.get("Via");via=via.split("\\s+")[1];via=via.split(";")[0];deviceInfo.setLocalIp(via.split(":")[0]);deviceInfo.setLocalPort(via.split(":")[1]);//断流检测//bye(map.get("deviceId"));}*/}
}

推流文本

//推流需要发起invite请求 如果是大华的话这里的所有设备编号要换为通道id编号,所以为了方便起见,直接设置设备编号和通道id编号一样,海康的通道id无法设置,所以直接发设备编号也可以获取到流
INVITE sip:34020000001110000003@192.168.1.8:5060;transport=udp SIP/2.0
Call-ID: 34020000001110000003
CSeq: 1 INVITE
From: <sip:34020000002000000001@0.0.0.0:5060>;tag=live
To: "34020000001110000003" <sip:34020000001110000003@192.168.1.8:5060> //to这里不要加tag,加了的话海康会返回找到id,大华则没事
Via: SIP/2.0/UDP 0.0.0.0:5060;branch=branchlive
Max-Forwards: 70
Content-Type: Application/sdp //消息类型
Contact: <sip:34020000002000000001@0.0.0.0:5060>
Supported: 100re1 //这个好像不影响,百度的
Subject: 34020000001110000003:0100000003,34020000002000000001:0 //海康要加该请求头
User-Agent: fyl //自定义的一些信息
Content-Length: 232 //这里要计算的消息体大小v=0
o=34020000001110000003 0 0 IN IP4 192.168.1.201
s=Play
c=IN IP4 192.168.1.201 //流媒体服务器ip
t=0 0
m=video 10002 TCP/RTP/AVP 96 98 97 //这里的TCP/RTP/AVP代表是tcp推流模式(tcp分为主动tcp和被动tcp,默认直接就是摄像头推流),udp推流为RTP/AVP  10002是流媒体服务器的端口号,建议偶数端口
a=recvonly
a=rtpmap:96 PS/90000
a=rtpmap:98 H264/90000
a=rtpmap:97 MPEG4/90000
y=0100000003 //这里y是ssrc信息,采用多个设备共享一个推流端口时需要使用这个标识来判断是那个摄像头的流
f=//当我们发起推流请求之后,摄像头会回复3次信息,海康只回复2次,我们看最后一次信息回复就行
SIP/2.0 100 Trying
Via: SIP/2.0/UDP 0.0.0.0:5060;branch=branchlive;received=192.168.1.201
From: <sip:34020000002000000001@0.0.0.0:5060>;tag=live
To: "34020000001110000003" <sip:34020000001110000003@192.168.1.8:5060>
Call-ID: 34020000001110000003
CSeq: 1 INVITE
User-Agent: Dahua SIP UAS V1.0
Content-Length: 0//第二次回复,测试的海康摄像头没有这个信息回复
SIP/2.0 101 Dialog Establishement
Via: SIP/2.0/UDP 0.0.0.0:5060;branch=branchlive;received=192.168.1.201
From: <sip:34020000002000000001@0.0.0.0:5060>;tag=live
To: "34020000001110000003" <sip:34020000001110000003@192.168.1.8:5060>;tag=29937641
Call-ID: 34020000001110000003
CSeq: 1 INVITE
Contact: <sip:34020000001110000003@192.168.1.8:5060>
User-Agent: Dahua SIP UAS V1.0
Content-Length: 0//最后一次回复,注意这次回复,如果是200说明请求成功,只要我们回复ack摄像头就会开始推流,如果不是200说明信息有误,一般都是设备编号或者通道编号,再或者tag的一些信息有误,海康对tag比较敏感
SIP/2.0 200 OK
Via: SIP/2.0/UDP 0.0.0.0:5060;branch=branchlive;received=192.168.1.201
From: <sip:34020000002000000001@0.0.0.0:5060>;tag=live //这个字段要保存起来
To: "34020000001110000003" <sip:34020000001110000003@192.168.1.8:5060>;tag=29937641 //这个字段要也保存起来,可以看到tag是摄像头生成的
Call-ID: 34020000001110000003 //同上
CSeq: 1 INVITE //注意这里的标识,这个标识跟推流请求一样说明是对应推流的回复
Contact: <sip:34020000001110000003@192.168.1.8:5060>
User-Agent: Dahua SIP UAS V1.0
Content-Type: application/sdp
Content-Length:   260v=0
o=34020000001110000003 0 0 IN IP4 192.168.1.8
s=Play
i=VCam Live Video
c=IN IP4 192.168.1.8
t=0 0
m=video 9702 TCP/RTP/AVP 96
a=sendonly
a=rtpmap:96 PS/90000
a=streamprofile:0
a=setup:active
a=connection:new
y=0100000003
f=v/0/0/0/0/0a/0/0/0//收到200回复之后,保存from,to和Call-ID 这三个字段在断流时需要用到,如果三个字段有变动都无法进行断流,特别是海康,差一点都不行,会回复找不到callid
ACK sip:34020000001110000003@192.168.1.8:5060 SIP/2.0
Call-ID: 34020000001110000003
CSeq: 1 ACK
Via: SIP/2.0/UDP 34020000002000000001:5060;branch=z9hG4bK-353633-cc1d42f582e2ec40dfedb6812f9ab1b8
From: <sip:34020000002000000001@0.0.0.0:5060>;tag=live
To: "34020000001110000003" <sip:34020000001110000003@192.168.1.8:5060>;tag=29937641
Max-Forwards: 70
Content-Length: 0//结束推流请求 把上面保存的值放进来
BYE sip:34020000001110000003@192.168.1.8:5060;transport=udp SIP/2.0
Call-ID: 34020000001110000003
CSeq: 6 BYE
From: <sip:34020000002000000001@0.0.0.0:5060>;tag=live
To: "34020000001110000003" <sip:34020000001110000003@192.168.1.8:5060>;tag=29937641
Via: SIP/2.0/UDP 0.0.0.0:5060;branch=branchbye
Contact: <sip:34020000002000000001@0.0.0.0:5060>
Max-Forwards: 70
Content-Length: 0//摄像头回复200说明断流成功
SIP/2.0 200 OK
Via: SIP/2.0/UDP 0.0.0.0:5060;branch=branchbye;received=192.168.1.201
From: <sip:34020000002000000001@0.0.0.0:5060>;tag=live
To: "34020000001110000003" <sip:34020000001110000003@192.168.1.8:5060>;tag=29937641
Call-ID: 34020000001110000003
CSeq: 6 BYE
User-Agent: Dahua SIP UAS V1.0
Content-Length: 0


由于摄像头是公司的画面,就不放流画面效果图了

上面涉及到的信息模板

//请求推流  Stream-ID: stream:34020000001110000003:34020000001110000002
private static final String str_invite="INVITE sip:{deviceId}@{deviceLocalIp}:{deviceLocalPort};transport=udp SIP/2.0"+n+"Call-ID: {Call-ID}"+n+"CSeq: 1 INVITE"+n+"From: <sip:{serverId}@{serverIp}:{serverPort}>;tag=live"+n+"To: \"{deviceId}\" <sip:{deviceId}@{deviceLocalIp}:{deviceLocalPort}>"+n+"Via: SIP/2.0/UDP {serverIp}:{serverPort};branch=branchlive"+n+"Max-Forwards: 70"+n+"Content-Type: Application/sdp"+n+"Contact: <sip:{serverId}@{serverIp}:{serverPort}>"+n+"Supported: 100re1"+n+/*"Stream-ID: stream:{deviceId}:34020000001110000002"+n+*/"Subject: {deviceId}:010000{ssrc},{serverId}:0"+n+"User-Agent: fyl"+n+"Content-Length: {Content-Length}"+n+n;
//tcp
private static final String str_invite_sdp="v=0"+n+"o={deviceId} 0 0 IN IP4 {mediaServerIp}"+n+"s=Play"+n+"c=IN IP4 {mediaServerIp}"+n+"t=0 0"+n+"m=video {mediaServerPort} TCP/RTP/AVP 96 98 97"+n+"a=recvonly"+n+"a=rtpmap:96 PS/90000"+n+"a=rtpmap:98 H264/90000"+n+"a=rtpmap:97 MPEG4/90000"+n+"y=010000{ssrc}"+n+"f="+n+n;
//udp
private static final String str_invite_sdp_udp="v=0"+n+"o={deviceId} 0 0 IN IP4 {mediaServerIp}"+n+"s=Play"+n+"c=IN IP4 {mediaServerIp}"+n+"t=0 0"+n+"m=video {mediaServerPort} RTP/AVP 96 98 97"+n+"a=recvonly"+n+"a=rtpmap:96 PS/90000"+n+"a=rtpmap:98 H264/90000"+n+"a=rtpmap:97 MPEG4/90000"+n+"y=010000{ssrc}"+n+"f="+n+n;//回复ack
private static final String str_ack="ACK sip:{deviceId}@{deviceLocalIp}:{deviceLocalPort} SIP/2.0"+n+"Call-ID: {Call-ID}"+n+"CSeq: 1 ACK"+n+"Via: SIP/2.0/UDP {serverIp}:{serverPort};branch={branchId}"+n+"From: {From}"+n+"To: {To}"+n+"Max-Forwards: 70"+n+"Content-Length: 0"+n+n;private static final String str_bye="BYE sip:{deviceId}@{deviceLocalIp}:{deviceLocalPort};transport=udp SIP/2.0"+n+"Call-ID: {Call-ID}"+n+"CSeq: 6 BYE"+n+"From: {From}"+n+"To: {To}"+n+"Via: SIP/2.0/UDP {serverIp}:{serverPort};branch=branchbye"+n+"Contact: <sip:{serverId}@{serverIp}:{serverPort}>"+n+"Max-Forwards: 70"+n+"Content-Length: 0"+n+n;

推流代码实现

//下达推流指令
public  void play(String deviceId){//检查udp通道if(channel==null){return;}logger.info("play start");try {//获取设备id对应的设备信息DeviceInfo deviceInfo=getDeviceInfo(deviceId);if(deviceInfo==null){return;}//获取ip和端口(这里如果是公网的话保存的是公网的ip和端口,而公网ip和端口有时限的,必须通过保活心跳来实时更新ip和端口,以保证能通讯到内网,所以心跳信息间隔建议不超过1分钟)InetSocketAddress inetSocketAddress=new InetSocketAddress(deviceInfo.getIp(),deviceInfo.getPort());String sendStr=null;//获取推流信息模板sendStr=str_invite;//String callID=Utils.getCallID(serverIp);String callID=deviceId;String ssrc=deviceId.substring(deviceId.length()-4);sendStr=replaceAll("{Call-ID}", callID,sendStr);sendStr=replaceAll("{deviceId}", deviceId,sendStr);sendStr=replaceAll("{ssrc}", ssrc,sendStr);sendStr=replaceAll("{deviceLocalIp}", deviceInfo.getLocalIp(),sendStr);sendStr=replaceAll("{deviceLocalPort}", deviceInfo.getLocalPort(),sendStr);sendStr=replaceAll("{serverId}", serverId,sendStr);sendStr=replaceAll("{serverIp}", serverIp,sendStr);sendStr=replaceAll("{serverPort}", serverPort,sendStr);String sendStr2=str_invite_sdp;sendStr2=replaceAll("{deviceId}", deviceId,sendStr2);sendStr2=replaceAll("{ssrc}", ssrc,sendStr2);sendStr2=replaceAll("{mediaServerIp}", mediaServerIp,sendStr2);sendStr2=replaceAll("{mediaServerPort}", mediaServerPort,sendStr2);sendStr=sendStr+sendStr2;//String.valueOf(sendStr2.getBytes().length)sendStr=replaceAll("{Content-Length}", String.valueOf(sendStr2.getBytes().length),sendStr);if(sendStr!=null){send(sendStr,inetSocketAddress);Data.putScheduled(new SendTipsTask(deviceId + "下达推流指令。。。"));}}catch (Exception e){logger.error("play error",e);}
}//拼装字符串方法,性能比较低,可以自行替换,大概的方向是这样
private static String replaceAll(String target,String replacement,String source){if(target.contains("{")){target=target.replace("{","\\{");}if(target.contains("}")){target=target.replace("}","\\}");}Pattern p = Pattern.compile(target);Matcher m = p.matcher(source);return m.replaceAll(replacement);
}

回复ack

//摄像头响应信息  一般出现 在下达指令之后返回的
if("RESPONSE".equals(map.get("messageType"))){if("200".equals(map.get("stateCode"))){//cseq是INVITE说明是推流指令回复 需要回复ack进行推流if(map.get("CSeq").contains("INVITE")){//回复acksendStr=str_ack;sendStr=replaceAll("{deviceId}",map.get("deviceId"),sendStr);sendStr=replaceAll("{deviceLocalIp}",map.get("deviceLocalIp"),sendStr);sendStr=replaceAll("{deviceLocalPort}",map.get("deviceLocalPort"),sendStr);sendStr=replaceAll("{Call-ID}",map.get("Call-ID"),sendStr);sendStr=replaceAll("{serverIp}",serverId,sendStr);sendStr=replaceAll("{serverPort}",serverPort,sendStr);sendStr=replaceAll("{branchId}", Utils.getBranchId(),sendStr);sendStr=replaceAll("{From}",map.get("From"),sendStr);sendStr=replaceAll("{To}",map.get("To"),sendStr);String deviceId=map.get("deviceId");//保存推流信息,以便断流使用deviceInfo.setLiveCallID(map.get("Call-ID"));deviceInfo.setLiveFromInfo(map.get("From"));deviceInfo.setLiveToInfo(map.get("To"));deviceInfo.setSsrc(Utils.getSsrc("010000"+deviceId.substring(deviceId.length()-4)));deviceInfo.setLive(true);Data.putScheduled(new SendTipsTask(deviceId + "回复推流ack指令。。。"));Data.putScheduled(new SendDataTask(commonService));}//断流指令收到的回复 推流的所有参数都要保存,有些摄像头会校验参数(海康断流必须和推流参数基本一致,大华则不用)if(map.get("CSeq").contains("BYE")){if(deviceInfo.getLiveCallID()!=null&&map.get("Call-ID").equals(deviceInfo.getLiveCallID())){deviceInfo.setLive(false);Data.putScheduled(new SendTipsTask(map.get("deviceId") + "推流已关闭。。。"));Data.putScheduled(new SendDataTask(commonService));}}}
}

断流

//断流
public void bye(String deviceId){if(channel==null){return;}logger.info("bye start");try {DeviceInfo deviceInfo=getDeviceInfo(deviceId);if(deviceInfo==null){return;}InetSocketAddress inetSocketAddress=new InetSocketAddress(deviceInfo.getIp(),deviceInfo.getPort());String sendStr=null;sendStr=str_bye;String callID=deviceInfo.getLiveCallID();if(callID==null){callID=deviceId;}sendStr=replaceAll("{Call-ID}", callID,sendStr);sendStr=replaceAll("{deviceId}", deviceId,sendStr);sendStr=replaceAll("{deviceLocalIp}", deviceInfo.getLocalIp(),sendStr);sendStr=replaceAll("{deviceLocalPort}", deviceInfo.getLocalPort(),sendStr);sendStr=replaceAll("{serverId}", serverId,sendStr);sendStr=replaceAll("{serverIp}", serverIp,sendStr);sendStr=replaceAll("{serverPort}", serverPort,sendStr);sendStr=replaceAll("{From}", deviceInfo.getLiveFromInfo(),sendStr);sendStr=replaceAll("{To}", deviceInfo.getLiveToInfo(),sendStr);if(sendStr!=null){send(sendStr,inetSocketAddress);}}catch (Exception e){logger.error("bye error",e);}
}

gb28181的sip服务器简单实现就这么点了,当然还有获取什么设备信息的指令,ptz的指令,方法都是差不多,只要自己通过抓包就可以知道通讯的流程。

下节简单讲解java解包gb28181摄像头传输过来的rtp over tcp/udp流,并通过javacv退出rtmp

gb28181简单实现sip信令服务器(java版基于springboot):四、sip摄像头心跳保活、推流(tcp/udp)和结束推流相关推荐

  1. gb28181简单实现sip信令服务器(java版基于springboot):一、netty创建udp服务器

    以下仅代表个人理解,仅供参考,欢迎大佬纠正!!! maven依赖 <!-- springboot配置依赖 --> <dependency><groupId>org. ...

  2. gb28181简单解包rtp ps流,推出rtmp(java版基于springboot):六、解包rtp ps流,推出rtmp

    解析流程参考 https://blog.csdn.net/chen495810242/article/details/39207305 代码基于github上的修改 https://github.co ...

  3. Java版基于springboot+maven海康摄像头sdk抓拍功能的二次开发

    相信在用Java开发海康摄像头的小袁袁都很苦恼官网给的SDK,官方的SDK中有demo,有文档,可那大多都是基于C写的文档,找了半天终于找到一个java版本的,进去一看还是用Jfream写的C/S版的 ...

  4. java语言基于springboot+vue+elementUI 4S店车辆管理系统-#计算机毕业设计

    项目介绍 随着信息化的不断深入,经济的飞速发展,企业要想在激烈的市场竞争中立于不败之地,没有现代化的管理是万万不行的.汽车4S营销管理,作为一种新型的以品牌为单位个体经营管理为主体,同时集系统管理.采 ...

  5. java语言基于springboot+vue+elementUI 基于web的智慧养老平台-#计算机毕业设计

    项目介绍 随着社会的发展我国的人口老龄化严重,为了让这些在年前是给社会做出过贡献的老人老有所依,老有所养,度过一个安详的晚年,很多地方都实现了智慧养老,为此我们通过springboot+vue+ele ...

  6. java语言基于springboot+vue+elementUI 毕业生实习管理系统-#计算机毕业设计

    项目介绍 每年都有大量的毕业生,毕业生面临的一个很严峻的问题就是如何去进行实习.我经过了实习,用人单位才能够更好的让实习生进行工作. 传统的毕业生实习管理都是通过人工手动的方式进行管理的.这种管理模式 ...

  7. wifidog java_家用环境下部署wifidog认证服务器(java版)

    本文所讲的是基于一个java版wifidog认证服务器的开源项目,在windows环境下搭建wifidog认证服务器配合apfree固件实现用户名密码的认证. 大致步骤如下: 一,准备 1.搭建硬件及 ...

  8. 悟空CRM java版(基于jfinal+vue+ElementUI的前后端分离CRM系统)

    CRM9.0(JAVA版) 软件长期为企业提供企业管理软件(CRM/HRM/OA/ERP等)的研发.实施.营销.咨询.培训.服务于一体的信息化服务.悟空软件以高科技为起点,以技术为核心.以完善的售后服 ...

  9. Python核心编程(第3版)第2章网络编程中关于tcp/udp服务器和客户端实现代码的运行出错的修正

    在Python核心编程(第3版)第2章网络编程中, 关于tcp/udp服务器和客户端实现代码的运行会出现 ['str' does not support the buffer interface]之类 ...

最新文章

  1. JVM:StringTable
  2. 语言学与计算机应用学什么,2016考研专业:语言学及应用语言学
  3. 2013 多校联合4 1011 Fliping game (hdu 4642)
  4. 打入硅谷的局外人|Decode the Week
  5. redis核心技术与实战(二)缓存应用篇
  6. 山寨一个ini文件的解析器
  7. C++ 遍历 iterator has no member named
  8. 在企业级开发中使用Try...Catch...会影响效率吗?
  9. Navicat基础mysql语法
  10. excel计算数据的差和的公式和方法、相关系数、绝对误差
  11. 【Prometheus】Prometheus联邦的一次优化记录[续]
  12. 什么是云服务?vivo云服务是什么意思?
  13. 阿里云服务器采用AMD CPU处理器ECS实例规格详解
  14. 【安装配置Git】最新版Git安装教程
  15. 最少拍无纹波计算机控制系统,最少拍无纹波计算机控制系统设计.doc
  16. 痞子衡嵌入式:IAR内部C-SPY调试组件配套宏文件(.mac)用法介绍
  17. 音视频之AudioTrack的使用
  18. tp5.1实现Token验证
  19. Python 爬虫系列:爬取全球机场信息
  20. 058输入月份号输出英文月份名

热门文章

  1. System Generator简介
  2. 微信语音怎么转发详细转发教程详解
  3. 打开mac系统偏好设置
  4. 王童:知行合一 · 当大数据遇到生物学 | 优秀毕业生专访
  5. android so调用java,AndServer+Service打造Android服务器实现so文件调用
  6. java代码重构工具_代码重构什么意思 Java代码重构的几种模式
  7. python之矩阵相加:提示用户输入矩阵的行数n,再提示用户输入矩阵的列数m,接下来,提示用户输入 2*n*m 个数字(每次输入 一个数字)。输出 C=A+B。
  8. 数学分析教程史济怀练习16.3
  9. 网络攻防技术——OSI安全体系
  10. mysql分表插件_分库分表简单?那我想问如何实现“分库分表插件”?