代码只对海康和大华做了一些适配,仅供学习参考。不同摄像头可能不太一样,可以自行抓包适配。解析消息直接是字符串操作,比较方便,不依赖第三包。

摄像头注册代码

//接收到netty的信息后需要进行gbk解码
String str=packet.content().toString(Charset.forName("gbk"));
//去除空包,公网状态下很多空包 应该是服务器端口被探测
if(str.trim().length()==0){return;
}
//打印接收信息
logger.info("-------------");
logger.info(n+str);
logger.info(packet.sender().getAddress().getHostAddress());
logger.info(String.valueOf(packet.sender().getPort()));
logger.info("-------------");String sendStr=null;//re(str) 解析收到的字符串
//这里可以验证设备编号
Map<String,String> map=re(str);
if(!map.get("deviceId").startsWith("3402000000111")){return;
}//getDeviceInfo(map.get("deviceId")) 从redis获取上次保存的最新信息
//获取缓存中得设备信息 里面包含了设备所有保存的信息
DeviceInfo deviceInfo=getDeviceInfo(map.get("deviceId"));
//数据为空就实例化一个
if(deviceInfo==null){deviceInfo=new DeviceInfo();
}
//设置最新的ip和端口,公网情况下ip和端口是有时间限制的,所以必须保存最新的ip和端口
deviceInfo.setIp(packet.sender().getAddress().getHostAddress());
deviceInfo.setPort(packet.sender().getPort());
//保存最后通信时间以作过期判断
deviceInfo.setTime(System.currentTimeMillis());

根据上一节收到的数据样式进行简单的解析

private static Map<String,String> re(String str){Map<String,String> map=new HashMap<>();BufferedReader br = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(str.getBytes(Charset.forName("gbk"))), Charset.forName("gbk")));//记录读取的行数Integer count=0;try {String line=null;//记录是否遇到空行,消息体和消息头中间会有个换行隔开boolean flag=false;while ((line=br.readLine())!=null){count=count+1;if("".equals(line)){flag=true;continue;}if(flag){//获取MESSAGE 对应的CmdType,这里常见的是keeplive(保护心跳)if(line.contains("<CmdType>")){//去掉空字符串(测试的情况是海康的有空格,大华没有)line=line.trim();line=line.replace("<CmdType>","");line=line.replace("</CmdType>","");map.put("CmdType",line);}continue;}if(count==1){//第一行信息 SIP/2.0结尾的是摄像头请求信息if(line.endsWith("SIP/2.0")){//获取消息类型map.put("method",line.split("\\s+")[0].trim());map.put("messageType","REQUEST");continue;}//SIP/2.0开头的是摄像头响应的信息if(line.startsWith("SIP/2.0")){//响应码获取map.put("stateCode",line.split("\\s+")[1]);//消息类型map.put("method",line.split("\\s+")[2].trim());map.put("messageType","RESPONSE");continue;}}//冒号加空格切割请求头属性和值String[] s=line.split(": ");//第一个是请求头名称,第二个是值map.put(s[0].trim(),s[1].trim());//From请求头,如果是请求信息,通常设备编号在这里获取if("From".equals(s[0])){if("REQUEST".equals(map.get("messageType"))){String deviceId=s[1].split(";")[0];deviceId=deviceId.split(":")[1];deviceId=deviceId.replace(">","");deviceId=deviceId.split("@")[0];map.put("deviceId",deviceId);logger.info(deviceId);}}if("To".equals(s[0])){if("RESPONSE".equals(map.get("messageType"))&&!map.get("Via").contains("branchbye")){String deviceId=s[1].split("\\s+")[0];deviceId=deviceId.replaceAll("\"","");map.put("deviceId",deviceId);String deviceLocalIp=s[1].split("\\s+")[1];deviceLocalIp=deviceLocalIp.split(";")[0];deviceLocalIp=deviceLocalIp.split("@")[1];deviceLocalIp=deviceLocalIp.replace(">","");map.put("deviceLocalIp",deviceLocalIp.split(":")[0]);map.put("deviceLocalPort",deviceLocalIp.split(":")[1]);}//这里区分是否是下达推流结束指令的的响应     if("RESPONSE".equals(map.get("messageType"))&&map.get("Via").contains("branchbye")){String deviceId=s[1].split(";")[0];deviceId=deviceId.split("@")[0];deviceId=deviceId.replace("<sip:","");map.put("deviceId",deviceId);}}}}catch (Exception e){e.printStackTrace();}//设备编号处理特殊情况,有时包含空格if(map.get("deviceId").split("\\s+").length>1){map.put("deviceId",map.get("deviceId").split("\\s+")[1]);}return  map;}

定义注册响应模板

{value} 这种格式的字符串用于替换真实的值
realm="3402000000"是填写sip域,这里直接赋值,可根据情况修改

//换行符
private static final String n="\r\n";//摄像头第一次注册401回复验证
private static final String str_401="SIP/2.0 401 Unauthorized"+"\r\n"+"CSeq: 1 REGISTER"+"\r\n"+"Call-ID: {Call-ID}"+"\r\n"+"From: {From}"+"\r\n"+"To: {To}"+"\r\n"+"Via: {Via}"+"\r\n"+"WWW-Authenticate: Digest realm=\"3402000000\",nonce=\"{nonce}\""+"\r\n"+"Content-Length: 0"+"\r\n"+"\r\n";//第二次注册成功回复200 ok
private static final String str_200_ok="SIP/2.0 200 OK"+"\r\n"+"CSeq: 2 REGISTER"+"\r\n"+"Call-ID: {Call-ID}"+"\r\n"+"From: {From}"+"\r\n"+"To: {To}"+"\r\n"+"Via: {Via}"+"\r\n"+"Expires: 3600"+"\r\n"+"Date: {Date}"+"\r\n"+"Content-Length: 0"+"\r\n"+"\r\n";

注册响应

 //摄像头注册处理
if("REGISTER".equals(map.get("method"))){//第一次注册,回复401if("1 REGISTER".equals(map.get("CSeq"))){//该字符串是用于回复摄像头请求信息sendStr=str_401;//这里的nonce是一串随机数,随便自己生成String nonce = DigestUtils.md5Hex(map.get("Call-ID")+map.get("deviceId"));sendStr=sendStr.replace("{nonce}",nonce);//大部分的字段回复直接拷贝请求头里面的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"));//重置推流状态deviceInfo.setLive(false);}else if("2 REGISTER".equals(map.get("CSeq"))){//第二次注册,这里可以验证是否是自己的摄像机,我这边直接通过//关于如何验证密码下面再说明,这里直接通过sendStr=str_200_ok;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"));//生成时间sendStr=sendStr.replace("{Date}",getGMT());String contact=map.get("Contact");contact=contact.split("@")[1];contact=contact.replace(">","");/* Map<String,String> d=new HashMap<>(2);d.put("deviceLocalIp",contact.split(":")[0]);d.put("deviceLocalPort",contact.split(":")[1]);*/deviceInfo.setLocalIp(contact.split(":")[0]);deviceInfo.setLocalPort(contact.split(":")[1]);String deviceId=map.get("deviceId");//注册成功直接生成ssrc,方便按需推流,这里是取设备编号后四位加前缀,然后取十六进制字符串deviceInfo.setSsrc(Utils.getSsrc("010000"+deviceId.substring(deviceId.length()-4)));//业务通知事件,可忽略Data.putScheduled(new SendTipsTask(deviceId + "注册成功。。。"));Data.putScheduled(new SendDataTask(commonService));//断流检测//bye(map.get("deviceId"));}else if("0".equals(map.get("Expires"))){//注销String deviceId=map.get("deviceId");//业务通知事件,可忽略Data.putScheduled(new SendTipsTask(deviceId + "注销。。。"));deviceInfo.setLive(false);Data.putScheduled(new SendDataTask(commonService));}else if(!"0".equals(map.get("Expires"))){//重复注册,直接回复200,如果回复401会造成断流sendStr=str_200_ok;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"));sendStr=sendStr.replace("{Date}",getGMT());String contact=map.get("Contact");contact=contact.split("@")[1];contact=contact.replace(">","");/* Map<String,String> d=new HashMap<>(2);d.put("deviceLocalIp",contact.split(":")[0]);d.put("deviceLocalPort",contact.split(":")[1]);*/deviceInfo.setLocalIp(contact.split(":")[0]);deviceInfo.setLocalPort(contact.split(":")[1]);String deviceId=map.get("deviceId");deviceInfo.setSsrc(Utils.getSsrc("010000"+deviceId.substring(deviceId.length()-4)));}
}

注册验证机制

发送过去验证的参数系要SIP域(realm)和一串随机数(nonce)

public static void main(String[] args) {//ha1=md5(username:realm:password)//ha2=md5(Method:Uri)//RESPONSE=md5(HA1:nonce:HA2)//摄像头id 第二次注册参数会携带String username="34020000001110000003";//SIP域,要跟摄像头里填写的一致String realm="3402000000";//存在服务器和摄像头填写的注册密码,验证时必须要一致String password="123456";//发送给摄像头的随机数,第二次注册时摄像头回复也会携带该参数String nonce="962535b552b6e29883ff988c0065ddc2";//请求方法,这里直接就是注册String Method="REGISTER";//uri是第二次注册是的链接,第二次注册时摄像头回复也会携带该参数String Uri="sip:34020000002000000001@192.168.1.201:5060";//先生成ha1String ha1= DigestUtils.md5Hex(username+":"+realm+":"+password);//ha2String ha2= DigestUtils.md5Hex(Method+":"+Uri);//生成最终结果,然后对比摄像头传来的response属性,具体情形可参考上一节的注册信令解读//如果md5一致就是说明密码正确,然后验证通过,否则返回404给摄像头,或者不理会,再或者继续回复401要求验证密码等信息System.out.println(DigestUtils.md5Hex(ha1+":"+nonce+":"+ha2));
}
下一节心跳保活回复

gb28181简单实现sip信令服务器(java版基于springboot):三、sip摄像头注册(代码实现)相关推荐

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

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

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

    心跳文本 //摄像头发送过来的Keepalive保活信息 MESSAGE sip:34020000002000000001@192.168.1.201:5060 SIP/2.0 //MESSAGE 方 ...

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

最新文章

  1. 虚拟方法及抽象方法在使用上的区别
  2. centos7升级自带的php5.4版本到php5.6
  3. android studio on/off 切换开关,如何使用android studio中的开关小部件来启用/禁用函数()?...
  4. 11届蓝桥杯青少年组C++全国赛高级组 八.编程实现:计数(python3实现)
  5. Linux crontab
  6. Hbase+Phoenix使用总结
  7. 个人代码库のC#可移动按钮“相关代码”
  8. mongodb的学习过程
  9. Hexo博客中添加Live 2D模型
  10. LabVIEW网络数据传输远程控制编程与验证测试
  11. ubuntu等linux发行版声卡、网卡、显卡驱动问题解决
  12. 永久删除计算机文件怎么操作步骤,如何彻底删除电脑中的文件 永久删除文件方法...
  13. Pycharm ssh远程调试报错: cannot connect to X server的解决方法
  14. 几个巧妙的电流检测电路
  15. 小米手机 miui 8.x开启开发者模式指导
  16. 尼尔森F形状网页浏览模式
  17. 小程序页面传值传递对象[Object Object]
  18. [学习笔记]Min-25筛
  19. 计算机硬件配置与bios设置,bios查看电脑配置 bios查看电脑的详细配置
  20. NVIDIA Jetson TX2 内核中添加 CP210x 串口驱动

热门文章

  1. QAbstractItemModel+qtreeview
  2. 学习记录:小程序图片上传至服务器
  3. VMware虚拟机安装Windows 10 详细教程
  4. 软件、硬件、模拟器,盘点九大机器人开源项目
  5. vue使用富文本编辑器vue-quill-editor
  6. 【已解决】java int转byte出现负数
  7. 毕业论文知网查重心得体会——吐血奉献
  8. tpadmin的坑收集 nginx下配置tp5失败
  9. TPAdmin 验证码不显示问题
  10. delphi 剪切板变量_delphi实时监控剪贴板