你的浏览器禁用了JavaScript, 请开启后刷新浏览器获得更好的体验!
首页
热门
推荐
精选
登录
|
注册
java整合海康威视、FFmpeg完成抓拍、实时视频播放、视频录制保存
立即下载
用AI写一个
金额:
9
元
支付方式:
友情提醒:源码购买后不支持退换货
立即支付
我要免费下载
发布时间:2020-06-04
27人
|
浏览:19477次
|
收藏
|
分享
技术:java FFmpeg 海康威视sdk
运行环境:windows
概述
java整合海康威视、FFmpeg完成抓拍、实时视频播放、视频录制保存本地
详细
## 写在最前 海康威视摄像头集成java开发虽然有很多资料,但是功能都比较分散,在网上找了一些demo, 并在大牛的配合下, 完成了这次整合海康的demo开发, 功能如题, 主要有实时视频前端页面播放, 抓拍图片前端展示, 保存摄像头视频到本地(两种方式: 1. 通过海康威视sdk, 2. 通过FFmpeg) 因为需求是图片和视频等都要在前端做展示, 所以demo也包含前端的示例 感谢@Hanlex-Liu和@叫我三胖哥哥在开发过程中给予的无私帮助 本文将从项目搭建开始 ## 一、开发环境 windows下 idea+springboot + FFmpeg + 海康sdk ## 二、初始项目搭建 首先在idea中创建spring boot项目时,勾选web 然后在海康威视官网下载最新的sdk开发包 将sdk下的所有dll及文件夹放在项目目录下(注:试过放在别的地方,但会影响功能导致失败,若有解决办法,请评论告知,不胜感谢) 如图: ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200602173855665.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1BvdHRlckRh,size_16,color_FFFFFF,t_70) 将海康sdk 给的java 的demo放入项目 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200602174142336.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1BvdHRlckRh,size_16,color_FFFFFF,t_70) 将sdk中的jar包放入项目,搭建时放入两个海康的即可 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200602174538903.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1BvdHRlckRh,size_16,color_FFFFFF,t_70) 配置中引入jar包和dll ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200602175118126.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1BvdHRlckRh,size_16,color_FFFFFF,t_70) 将所有的jar包和海康的dll(包括放入项目的海康的文件夹)都选中,确定即可 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200602175118113.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1BvdHRlckRh,size_16,color_FFFFFF,t_70) 更改demo中的dll文件位置, 在HCNetSDK.java中找到下面的两个并修改dll路径 ```javascript HCNetSDK INSTANCE = (HCNetSDK) Native.loadLibrary(System.getProperty("user.dir")+"\\HCNetSDK.dll", HCNetSDK.class); ``` ```javascript PlayCtrl INSTANCE = (PlayCtrl) Native.loadLibrary(System.getProperty("user.dir")+"\\PlayCtrl.dll", PlayCtrl.class); ``` 这样就搭建完成, 可以启动ClientDemo.java中的main方法 启动成功后会弹出一个窗口,输入你的摄像头账密ip等 先点击注册,再点击预览,即可看到当前摄像头的画面,证明你的项目初步搭建完成 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200602180038780.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1BvdHRlckRh,size_16,color_FFFFFF,t_70) ## 三、图片抓拍 前端页面预览 ![在这里插入图片描述](https://img-blog.csdnimg.cn/2020060218145438.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1BvdHRlckRh,size_16,color_FFFFFF,t_70) 因为图片要在前端展示,所以先配置一下web,直接上图了 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200602180557533.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1BvdHRlckRh,size_16,color_FFFFFF,t_70) 抓拍的代码 ```javascript @Service public class TakePhotoServiceImpl implements TakePhotoService { @Autowired private PhotoProperties properties; @Override public String cameraTakePhoto(CameraInfoEntity cameraInfoEntity) { //初始化设备 long startTime = System.currentTimeMillis(); HCNetSDK sdk = HCNetSDK.INSTANCE; if (!sdk.NET_DVR_Init()) { throw new RuntimeException("设备初始化获取失败"); } HCNetSDK.NET_DVR_DEVICEINFO_V30 devinfo = new HCNetSDK.NET_DVR_DEVICEINFO_V30(); //设备注册 NativeLong id = sdk.NET_DVR_Login_V30(cameraInfoEntity.getCameraIP(), cameraInfoEntity.getCameraPort(), cameraInfoEntity.getUserName(), cameraInfoEntity.getUserPwd(), devinfo); if (id.longValue() < 0) { throw new RuntimeException("设备注册失败"); } System.out.println("设备注册成功,用户id" + id); // 返回Boolean值,判断是否获取设备能力 HCNetSDK.NET_DVR_WORKSTATE_V30 devwork = new HCNetSDK.NET_DVR_WORKSTATE_V30(); if (!sdk.NET_DVR_GetDVRWorkState_V30(id, devwork)) { throw new RuntimeException("返回设备状态失败"); } startTime = System.currentTimeMillis(); //截屏对象 HCNetSDK.NET_DVR_JPEGPARA jpeg = new HCNetSDK.NET_DVR_JPEGPARA(); // 设置图片的分辨率 jpeg.wPicSize = 2; // 设置图片质量 jpeg.wPicQuality = 2; IntByReference reference = new IntByReference(); ByteBuffer jpegBuffer = ByteBuffer.allocate(1024 * 1024); SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss"); //创建文件夹 String path = properties.getPath(); SimpleDateFormat ymd = new SimpleDateFormat("yyyy-MM-dd"); String format = ymd.format(new Date()); String localPath = path + format; //声明文件 File file = new File(localPath); if (!file.exists()){ file.mkdir(); } //String fileNameString = "D:\\app\\application\\pic\\"+sdf.format(new Date())+ "_"+ cameraInfoEntity.getChannel() +".jpg"; String fileNameString = localPath + "\\" + sdf.format(new Date())+ "_"+ cameraInfoEntity.getChannel() + properties.getSuffix(); //String fileNameString = "cameracut_" + UUID.randomUUID().toString().replaceAll("-", "").toUpperCase() + sdf.format(new Date())+ "_"+ cameraInfoEntity.getChannel() +".jpg"; file = new File(fileNameString); boolean is = sdk.NET_DVR_CaptureJPEGPicture_NEW(id, cameraInfoEntity.getChannel(), jpeg, jpegBuffer, 1024 * 1024, reference); System.out.println("拍照获取图片用时" + (System.currentTimeMillis() - startTime) + "ms]"); System.out.println("拍照文件大小" + reference.getValue()); BufferedOutputStream outputStream = null; try { outputStream = new BufferedOutputStream(new FileOutputStream(file)); outputStream.write(jpegBuffer.array(), 0, reference.getValue()); outputStream.flush(); } catch (Exception e) { e.printStackTrace(); } finally { if (outputStream != null) { try { outputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } //InputStream inputStream = new BufferedInputStream(new FileInputStream(file)); //boolean upload = FTPUtils.ftpUpload("img.xxx.net", 21, "eGpZyE38rG0ExkGv", "Kq8J6GrTt1ngJ0HKeJ", "/vtsp/imgs", file.getName(), inputStream); //System.out.println("图片上传ftp,状态===========>" + upload); //删除图片文件 //if(file.exists() && file.isFile()) { // file.deleteOnExit(); //} //inputStream.close(); // //sdk.NET_DVR_Logout(id); //sdk.NET_DVR_Cleanup(); if (is) { return fileNameString; } else { return null; } } } ``` Controller中关于图片的方法 ①拍摄 ```javascript @RequestMapping("/takePhoto") @ResponseBody public Object takePhoto(){ System.out.println("开始操作图片抓拍!"); Map map = new HashMap(); CameraInfoEntity cameraInfoEntity = new CameraInfoEntity(); cameraInfoEntity.setCameraIP("192.168.1.65"); cameraInfoEntity.setCameraPort((short)8000); cameraInfoEntity.setUserName("admin"); cameraInfoEntity.setUserPwd("password"); cameraInfoEntity.setChannel(new NativeLong(1)); String fileName = takePhotoService.cameraTakePhoto(cameraInfoEntity); if (StringUtils.isNoneBlank(fileName)){ map.put("path", fileName); return reDataSuccess(map); } else { return reDefeatMsg("无参数"); } } ``` ②查看 ```javascript //图片转为base64编码 private String ImageToBase64(String imgPath) { byte[] data = null; // 读取图片字节数组 try { InputStream in = new FileInputStream(imgPath); data = new byte[in.available()]; in.read(data); in.close(); } catch (IOException e) { e.printStackTrace(); } // 对字节数组Base64编码 BASE64Encoder encoder = new BASE64Encoder(); // 返回Base64编码过的字节数组字符串 //System.out.println("本地图片转换Base64:" + encoder.encode(Objects.requireNonNull(data))); return encoder.encode(Objects.requireNonNull(data)); } @RequestMapping("/lookPhoto") @ResponseBody public String getPhoto(HttpServletResponse response,String filePath,String state) throws Exception { System.out.println("操作查看已保存的图片"); if (StringUtils.equals("1", state)){ filePath = photoProperties.getPath() + filePath; } System.out.println("图片路径为:" + filePath); response.setContentType("image/jpeg"); return ImageToBase64(filePath); } ``` js ```javascript //抓拍图片 function takePhoto() { $.ajax({ url:"/takePhoto", //data:{id:devId}, anysc:false, success:function(data){ var data = data.data; if (data.path == null){ alert("抓拍失败,请重试!") } else { var c = confirm("抓拍成功, 是否查看?") if (c == true) { var filePath = data.path; //var imgPathEncode = encodeURI(encodeURI(filePath)); // $('#img1').attr("src","../lookPhoto?filePath="+filePath); showPic(-1,filePath); } } } }); } ``` ```javascript //预览图片 function showPic(index,filePath) { var state = '0';//抓图 if (index != -1){ state = '1';//列表图 } $.ajax({ type: "POST", url:"/lookPhoto", data:{filePath:filePath, state:state}, anysc:false, success:function(res){ $("#img1").attr("src",""); $("#img1").attr("src","data:image/jpeg;base64," + res); $("#img1").attr("width", "500px"); $("#img1").attr("height","400px"); // $("#img1").attr("src",res); // $("#img").append(res); } }); } ``` 效果: ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200602181716957.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1BvdHRlckRh,size_16,color_FFFFFF,t_70) 抓拍结束; 注:代码中有实体类,都是简单封装了摄像头参数的,ip 端口 账密等 Properties类是springboot注解@ConfigurationProperties实现的 ```javascript photo: path: D:\app\application\pic\ suffix: .jpg video: inputPath: D:\app\application\video\ suffix: .MP4 isDelete: false outputPath: D:\app\application\video\ ffmpegPath: D:\Tools\ffmpeg\bin\ convertFlag: _wait dealFlag: _deal ``` ```javascript @Component @ConfigurationProperties(prefix = "photo") @Data public class PhotoProperties { private String path; private String suffix; } ``` ## 四、实时视频 大神写的很详细,贴上链接 链接: [https://blog.csdn.net/qq_36720088/article/details/82893924](https://blog.csdn.net/qq_36720088/article/details/82893924/) FFmpeg因为文件太大无法上传,可到官网下载最新,安装到本地后记得添加环境变量 ## 五、视频录制 ### 方式一、通过海康sdk录制 大神写的很详细,贴上链接 链接: [https://blog.csdn.net/weixin_44232093/article/details/96429942#comments_12209605](https://blog.csdn.net/weixin_44232093/article/details/96429942#comments_12209605) 优点:代码简洁,开始、结束录制 容易实现; 录制需要调用的方法: 注册-->开启预览-->开始保存:NET_DVR_SaveRealData 即可 结束录制:注册(和开始录制相同的ip账户)--> 结束保存:NET_DVR_StopSaveRealData即可 缺点(在我看来): 视频保存超过1024M时,会被拆分,在你的视频名称后加 _1.._2... 由于要在前端播放,通过读二进制流的方式,总是报错如下,![在这里插入图片描述](https://img-blog.csdnimg.cn/2020060309371944.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1BvdHRlckRh,size_16,color_FFFFFF,t_70) 百度不得其解,只能发邮件给海康,回复说通过海康sdk生成的视频,是通过MPEG-PS封装的,经查这种格式的视频无法被读取输出到前端,只能进行转码,无奈,转吧... 需要在本地安装FFmpeg,前面大神的链接有介绍,直接上代码: ```javascript @Service public class ConvertServiceImpl implements ConvertService { @Autowired private VideoProperties properties; /*private static String inputPath = ""; private static String outputPath = ""; private static String ffmpegPath = ""; void getPath() { // 先获取当前项目路径,在获得源文件、目标文件、转换器的路径 File diretory = new File(""); try { String currPath = diretory.getAbsolutePath(); inputPath = "D:\\app\\application\\video\\"; outputPath = "D:\\app\\application\\video\\output\\"; ffmpegPath = "D:\\Tools\\ffmpeg\\bin\\"; System.out.println(currPath); } catch (Exception e) { System.out.println("getPath出错"); } }*/ //判读格式 , 执行转换 boolean process(String filePath) { int type = checkContentType(filePath); boolean status = false; if (type == 0) { System.out.println("直接转成mp4格式"); status = processMp4(filePath);// 直接转成mp4格式 } else { System.out.println("未知的格式!!!"); } return status; } //private static boolean process() { // int type = checkContentType(); // boolean status = false; // if (type == 0) { // System.out.println("直接将文件转为flv文件"); // status = processFLV(PATH);// 直接将文件转为flv文件 // } else if (type == 1) { // String avifilepath = processAVI(type); // if (avifilepath == null) // return false;// avi文件没有得到 // status = processFLV(avifilepath);// 将avi转为flv // } // return status; //} @Override public boolean convert(String filePath) { long beginTime = System.currentTimeMillis(); String allPath = properties.getInputPath() + filePath; if (!checkfile(allPath)) { System.out.println(allPath + " is not file"); return false; } //判断视频格式 //try { // Encoder encoder = new Encoder(); // File target1 = new File(inputPath); // MultimediaInfo info3 = encoder.getInfo(target1); // System.out.println(info3); //} catch (EncoderException e) { // e.printStackTrace(); //} //**输出结果如下:** //it.sauronsoftware.jave.MultimediaInfo (format=mov, duration=115200, video=it.sauronsoftware.jave.VideoInfo (decoder=h264, size=it.sauronsoftware.jave.VideoSize (width=1280, height=720), bitRate=-1, frameRate=25.0), audio=it.sauronsoftware.jave.AudioInfo (decoder=mpeg4aac, samplingRate=44100, channels=2, bitRate=-1)) if (process(filePath)) { System.out.println("ok"); System.out.println("转换成功"); long endTime = System.currentTimeMillis(); long timeCha = (endTime - beginTime); String totalTime = sumTime(timeCha); System.out.println("转换视频格式共用了:" + totalTime + " "); if (properties.getIsDelete()) { deleteFile(allPath); } return true; } else { return false; } } public boolean checkfile(String path) { File file = new File(path); if (!file.isFile()) { return false; } return true; } public int checkContentType(String filePath) { String inputPath = properties.getInputPath() + filePath; String type = inputPath.substring(inputPath.lastIndexOf(".") + 1, inputPath.length()) .toLowerCase(); // ffmpeg能解析的格式:(asx,asf,mpg,wmv,3gp,mp4,mov,avi,flv等) if (type.equals("avi")) { return 0; } else if (type.equals("mpg")) { return 0; } else if (type.equals("wmv")) { return 0; } else if (type.equals("3gp")) { return 0; } else if (type.equals("mov")) { return 0; } else if (type.equals("mp4")) { return 0; } else if (type.equals("asf")) { return 0; } else if (type.equals("asx")) { return 0; } else if (type.equals("flv")) { return 0; } // 对ffmpeg无法解析的文件格式(wmv9,rm,rmvb等), // 可以先用别的工具(mencoder)转换为avi(ffmpeg能解析的)格式. else if (type.equals("wmv9")) { return 1; } else if (type.equals("rm")) { return 1; } else if (type.equals("rmvb")) { return 1; } return 9; } public String processAVI(int type) { String outputPath = properties.getOutputPath(); String ffmpegPath = properties.getFfmpegPath(); List
commend = new ArrayList
(); commend.add(ffmpegPath + "mencoder"); commend.add(properties.getInputPath()); commend.add("-oac"); commend.add("lavc"); commend.add("-lavcopts"); commend.add("acodec=mp3:abitrate=64"); commend.add("-ovc"); commend.add("xvid"); commend.add("-xvidencopts"); commend.add("bitrate=600"); commend.add("-of"); commend.add("mp4"); commend.add("-o"); commend.add(outputPath + "a.AVI"); try { ProcessBuilder builder = new ProcessBuilder(); Process process = builder.command(commend).redirectErrorStream(true).start(); new PrintStream(process.getInputStream()); new PrintStream(process.getErrorStream()); process.waitFor(); return outputPath + "a.AVI"; } catch (Exception e) { e.printStackTrace(); return null; } } public boolean processFlv(String oldfilepath) { String outputPath = properties.getOutputPath(); String ffmpegPath = properties.getFfmpegPath(); if (!checkfile(oldfilepath)) { System.out.println(oldfilepath + " is not file"); return false; } List
command = new ArrayList
(); command.add(ffmpegPath + "ffmpeg"); command.add("-i"); command.add(oldfilepath); command.add("-ab"); command.add("56"); command.add("-ar"); command.add("22050"); command.add("-qscale"); command.add("8"); command.add("-r"); command.add("15"); command.add("-s"); command.add("600x500"); command.add(outputPath + "a.flv"); try { // 方案1 // Process videoProcess = Runtime.getRuntime().exec(ffmpegPath + "ffmpeg -i " + oldfilepath // + " -ab 56 -ar 22050 -qscale 8 -r 15 -s 600x500 " // + outputPath + "a.flv"); // 方案2 Process videoProcess = new ProcessBuilder(command).redirectErrorStream(true).start(); new PrintStream(videoProcess.getErrorStream()).start(); new PrintStream(videoProcess.getInputStream()).start(); videoProcess.waitFor(); return true; } catch (Exception e) { e.printStackTrace(); return false; } } public boolean processMp4(String filePath) { String inputPath = properties.getInputPath(); String outputPath = properties.getOutputPath(); String ffmpegPath = properties.getFfmpegPath(); String outputName = filePath.replace(properties.getConvertFlag(), ""); List
command = new ArrayList
(); command.add(ffmpegPath + "ffmpeg"); command.add("-r"); command.add("25"); command.add("-i"); command.add(inputPath + filePath); command.add("-c:v"); command.add("libx264"); command.add("-mbd"); command.add("0"); command.add("-c:a"); command.add("aac"); command.add("-s"); command.add("1024*768"); command.add("-threads"); //指定同时启动线程执行数, 经测试到10再大速度几无变化 command.add("25"); command.add("-preset"); command.add("ultrafast"); command.add("-strict"); command.add("-2"); command.add("-pix_fmt"); command.add("yuv420p"); command.add("-movflags"); command.add("faststart"); command.add(outputPath + outputName); //command.add(ffmpegPath + "ffmpeg"); //command.add("-i"); //command.add(oldfilepath); //command.add("-c:v"); //command.add("libx264"); //command.add("-r"); //command.add("30"); //command.add("-b"); //command.add("6000k"); //command.add("-pix_fmt"); //command.add("yuv420p"); //command.add("-s"); //command.add("600x500"); //command.add(outputPath + "a.mp4"); //command.add("-i"); //command.add(oldfilepath);// 要转的文件路径 //command.add("-vcodec"); //command.add("h264"); // 设置自己想要的视频编码信息 //command.add("-acodec"); //command.add("copy");// 因为对音频没改变需求,所以直接copy //command.add("-f"); //command.add("mov"); //command.add("-y"); //command.add(outputPath + "a.mp4"); //command.add("-i"); //command.add(oldfilepath); //command.add("-c:v"); //command.add("libx264"); //command.add("-x264opts"); //command.add("force-cfr=1"); //command.add("-c:a"); //command.add("mp2"); //command.add("-b:a"); //command.add("256k"); //command.add("-vsync"); //command.add("cfr"); //command.add("-f"); //command.add("mpegts"); //command.add(outputPath + "a.mp4"); //command.add("-y"); // 该参数指定将覆盖已存在的文件 //command.add("-i"); //command.add(oldfilepath); //command.add("-c:v"); //command.add("libx264"); //command.add("-c:a"); //command.add("aac"); //command.add("-strict"); //command.add("-2"); //command.add("-pix_fmt"); //command.add("yuv420p"); //command.add("-movflags"); //command.add("faststart"); //command.add(outputPath + "a.mp4"); try { // 方案1 // Process videoProcess = Runtime.getRuntime().exec(ffmpegPath + "ffmpeg -i " + oldfilepath // + " -ab 56 -ar 22050 -qscale 8 -r 15 -s 600x500 " // + outputPath + "a.flv"); // 方案2 Process videoProcess = new ProcessBuilder(command).redirectErrorStream(true).start(); // new PrintStream(videoProcess.getErrorStream()).start(); new PrintStream(videoProcess.getInputStream()).start(); videoProcess.waitFor(); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 计算转码时间 * * @param ms * @return */ public String sumTime(long ms) { int ss = 1000; long mi = ss * 60; long hh = mi * 60; long dd = hh * 24; long day = ms / dd; long hour = (ms - day * dd) / hh; long minute = (ms - day * dd - hour * hh) / mi; long second = (ms - day * dd - hour * hh - minute * mi) / ss; long milliSecond = ms - day * dd - hour * hh - minute * mi - second * ss; String strDay = day < 10 ? "0" + day + "天" : "" + day + "天"; String strHour = hour < 10 ? "0" + hour + "小时" : "" + hour + "小时"; String strMinute = minute < 10 ? "0" + minute + "分" : "" + minute + "分"; String strSecond = second < 10 ? "0" + second + "秒" : "" + second + "秒"; String strMilliSecond = milliSecond < 10 ? "0" + milliSecond : "" + milliSecond; strMilliSecond = milliSecond < 100 ? "0" + strMilliSecond + "毫秒" : "" + strMilliSecond + " 毫秒"; return strDay + " " + strHour + ":" + strMinute + ":" + strSecond + " " + strMilliSecond; } /** * 删除文件方法 * * @param filepath */ public void deleteFile(String filepath) { File file = new File(filepath); if (file.delete()) { System.out.println("文件" + filepath + "已删除"); } } } class PrintStream extends Thread { InputStream __is = null; public PrintStream(InputStream is) { __is = is; } public void run() { try { while(this != null) { int _ch = __is.read(); if(_ch != -1) System.out.print((char)_ch); else break; } } catch (Exception e) { e.printStackTrace(); } } } ``` 红框是我认为比较重要的参数,这种转化的速度感人,如果加上-thread那四行参数,会比不加快上几十倍,但是,如果要转化上G的视频,速度仍是可想而知,况且海康还会把视频分块,得转化多次... ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200603095036109.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1BvdHRlckRh,size_16,color_FFFFFF,t_70) 这样转换过的视频就可以在前端播放了 读取视频二进制流代码: ```javascript @RequestMapping("stream") public void getStreamData(HttpServletResponse response,String fileName) { System.out.println("操作查看已保存视频" + fileName); String path = videoProperties.getInputPath(); String filePath = path + fileName; File file=new File(filePath); ServletOutputStream out=null; try { FileInputStream instream=new FileInputStream(file); byte[] b=new byte[1024]; int length=0; BufferedInputStream buf=new BufferedInputStream(instream); out=response.getOutputStream(); BufferedOutputStream bot=new BufferedOutputStream(out); while((buf.read(b))!=-1) { bot.write(b,0, b.length); } bot.flush(); bot.close(); instream.close(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } ``` 前端代码: ```javascript
your browser does not support the video tag
//播放 function openPlayer(filePath) { $("#video_play").show(); //禁用掉video的右键菜单 var video=document.getElementsByTagName("video")[0]; video.oncontextmenu=function(){ return false; } // video.src="showVideo?fileName="+filePath; video.src="stream?fileName="+filePath; } ``` ### 方式二、通过FFmpeg录制 优点: 生成的视频无需转换即可在前端播放,视频完整无分割 可以指定输出分辨率,视频大小稍微可控(500W摄像头录制8小时约3.5G) 缺点: 开始、结束录制比较麻烦,录制是通过代码操作 FFmpeg命令行命令,结束时必须获取相同的进程对象flush才不会损坏视频文件,有尝试过杀进程结束录制的 MP4格式文件会损坏,ts不会,但是ts无法在前端播放,所以需要将进程对象放入缓存,待需要结束录制时取出来,若摄像头数量过多,需考虑是否配置外置缓存... 另注:MP4格式的视频meta信息在文件尾部,在前端播放时,必须将视频全部读取完成才能在前端播放,3.5G视频约耗时3.5分钟才可读完播放,在网上找到一个小工具qt-faststart.exe, 可以将视频的meta信息放在头部,这样就能很快在前端播放了。经测,3.5G视频执行qt命令需耗时<1分钟,很不错了。 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200603100624668.png) 同样,qt工具的执行也需要通过代码执行cmd命令,录制和转换一起上代码: ```javascript @Service public class SaveVideoService{ @Autowired private VideoProperties properties; public boolean startSave(CameraInfo cameraInfo){ boolean status = false; try { String name = cameraInfo.getUserName(); String password = cameraInfo.getPwd(); String ip = cameraInfo.getAddress(); System.out.println("###使用进程的方式进行编码###"); //创建文件夹 String path = properties.getInputPath(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); String format = sdf.format(new Date()); String localPath = path + format; File file = new File(localPath); if (!file.exists()){ file.mkdir(); } // 1. 视频转换视频 //String rtspUrl = "rtsp://wangzhi:1qaz2wsx@192.168.104.251/h264/ch1/main/av_stream"; //String rtspUrl = "rtsp://"+name+":"+password+"@"+ip+"/h264/ch1/main/av_stream"; String rtspUrl = "rtsp://"+name+":"+password+"@"+ip+"/mpeg4/ch1/sub/av_stream"; SimpleDateFormat yyyyMMddHHmmss = new SimpleDateFormat("yyyyMMddHHmmss"); //String dest = "F:/"+sdf.format(now)+"—"+sdf.format(afterDate)+".mp4"; String dest = localPath + "\\test_"+yyyyMMddHHmmss.format(new Date())+ properties.getDealFlag() +".mp4"; System.out.println("录制生成的视频为: "+dest); String ipfrm = ip.replaceAll("\\.", ""); String key = ipfrm + name; System.out.println("进程的key为:" + key); status = convert(rtspUrl, dest, key); } catch (Exception e) { e.printStackTrace(); } return status; } boolean convert(String src,String dest, String key){ boolean result = false; String ffcmdpath = properties.getFfmpegPath() + "ffmpeg.exe"; try { StringBuilder cmd = new StringBuilder(); cmd.append(ffcmdpath) .append(" -rtsp_transport tcp ") // 使用tcp的命令,默认是udp .append(" -i ").append(src) // .append(" -vcodec h264 ") .append(" -vcodec copy ") .append("-b:v 200k ") .append("-bufsize 200k ") // .append(" -acodec copy ") // 音频,不设置好像也有。 //.append(" -s 1280*720 ") // 设置分辨率,关系到视频的大小或者为 -vf scale=iw/2:ih/2 // .append(" -vf scale=iw/2:ih/2 ") .append(" -y ") // 覆盖 .append(dest); System.out.println("cmd="+cmd.toString()); System.out.println("###start cmd="+new Date().toLocaleString()); Process process = Runtime.getRuntime().exec(cmd.toString()); //将进程PID放入缓存 //String processId = ProcessUtil.getProcessId(process); //Constants.videoSaveTask.put(VideoName,processId); //将进程对象放入缓存 Constants.videoSaveTaskObj.put(key,process); new PrintStreamV(process.getErrorStream()).start(); new PrintStreamV(process.getInputStream()).start(); process.waitFor(); System.out.println("###end cmd="+new Date().toLocaleString()); result = true; } catch (Exception e) { e.printStackTrace(); } return result; } /** * @title stopConvert * @date 2018年10月11日 * @return * @description 发送停止命令, * 但是要注意发送完成后,不一定会马上停止 。因为进程结束也是需要时间的 */ public boolean stopConvert(CameraInfo cameraInfo){ System.out.println("###开始结束"); try{ String ipfrm = cameraInfo.getAddress().replaceAll("\\.", ""); String key = ipfrm + cameraInfo.getUserName(); System.out.println("进程的key为:" + key); //方式1杀死进程 //String processId = Constants.videoSaveTask.get(VideoName); //ProcessUtil.killProcessByPid(processId); //方式2.进程正常退出 Process process = (Process) Constants.videoSaveTaskObj.get(key); OutputStream os = process.getOutputStream(); os.write("q".getBytes()); os.flush(); // 一定要刷新 System.out.println("###send q cmd "); }catch(Exception err){ err.printStackTrace(); return false; } return true; } /** * 视频转换 * (视频播放时待处理标识(处理后的视频可立即播放 qt-faststart.exe)) * @param filePath * @return */ public boolean convert(String filePath) { long beginTime = System.currentTimeMillis(); String allPath = properties.getInputPath() + filePath; if (!VideoUtils.checkfile(allPath)) { System.out.println(allPath + " is not file"); return false; } if (process(filePath)) { System.out.println("ok"); System.out.println("转换成功"); long endTime = System.currentTimeMillis(); long timeCha = (endTime - beginTime); String totalTime = VideoUtils.sumTime(timeCha); System.out.println("转换视频格式共用了:" + totalTime + " "); if (properties.getIsDelete()) { VideoUtils.deleteFile(allPath); } return true; } else { return false; } } boolean process(String filePath) { boolean status = false; try { String outputName = filePath.replace(properties.getDealFlag(), ""); String qtfaststart = properties.getFfmpegPath() + "qt-faststart.exe"; StringBuilder cmd = new StringBuilder(); if (Platform.isWindows()) { cmd.append(qtfaststart) .append(" "+ properties.getInputPath() + filePath+"") .append(" "+ properties.getInputPath() + outputName+""); } else if (Platform.isLinux() /*|| Platform.isAIX()*/) { // } System.out.println("cmd="+cmd.toString()); System.out.println("###start cmd="+new Date().toLocaleString()); Process process = Runtime.getRuntime().exec(cmd.toString()); new PrintStreamV(process.getErrorStream()).start(); new PrintStreamV(process.getInputStream()).start(); process.waitFor(); System.out.println("###end cmd="+new Date().toLocaleString()); return true; } catch (Exception e) { e.printStackTrace(); } return status; } } class PrintStreamV extends Thread { InputStream __is = null; public PrintStreamV(InputStream is) { __is = is; } public void run() { try { while(this != null) { int _ch = __is.read(); if(_ch != -1) System.out.print((char)_ch); else break; } } catch (Exception e) { e.printStackTrace(); } } } ``` FFmpeg生成的视频也有坑,若使用h264, 那么在录制过程中,因为h264所需的帧数量大,会造成进程内存消耗过大自动停止录制,并报错concealing 9 DC, 9 AC, 9 MV errors in I frame 所以使用 -vcodec copy 经测,视频录制一夜无事 ![在这里插入图片描述](https://img-blog.csdnimg.cn/2020060310244025.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1BvdHRlckRh,size_16,color_FFFFFF,t_70) 以上就是java整合海康威视实现一些基本功能的介绍,贴出的代码都是关键步骤,做个总结,后续若有新功能要加也会持续更新,有疑问可以评论中描述,看到的话会尽力解答。 最后,感谢那些无私帮助的大佬们,提供了很多思路和方向,十分感谢。
本实例支付的费用只是购买源码的费用,如有疑问欢迎在文末留言交流,如需作者在线代码指导、定制等,在作者开启付费服务后,可以点击“购买服务”进行实时联系,请知悉,谢谢
感谢
4
手机上随时阅读、收藏该文章 ?请扫下方二维码
相似例子推荐
评论
作者
Potter.Da
2
例子数量
36
帮助
7
感谢
评分详细
可运行:
4.5
分
代码质量:
4.5
分
文章描述详细:
4.5
分
代码注释:
4.5
分
综合:
4.5
分
作者例子
java整合海康威视、FFmpeg完成抓拍、实时视频播放、视频录制保存
java整合海康威视前端实时视频播放(flv.js+nginx)