(修改前言:最近公司需求又对接海康的人脸设备,也就刚刚好有时间重新整理写的这篇博客,看评论也有很多人说有问题 demo无法跑通 小编在这呢 也重新测一遍 也会将一些细节更详细的列出来吗 同时呢 除了之前写的这套方案,小编在这也会再提供一个方案)
写在前面:
最近也遇到了调用海康多个摄像头实现同时预览的需求,不`过官方demo里并没详细的案例,上网查了下资料,也找不到对应的解决方案 ,电话咨询海康技术,没接过,信息没回过。这里就不对海康技术支持多作评价了,废话不多说。上方案!
小编首先整理了文档和获取流程(如图)
然后小编根据这个思路将SDK的Demo流程整个看了一遍,终于发现了问题的所在,接下来直接上代码(个人是根据海康提供的demo进行简单的修改,将部分公共代码给抽了出来,另外小编只用到了预览录制和停止录制功能,解码和其他功能小编没用到,所以小编在这里都干掉了。尽量能简洁易懂就简介易懂,大佬勿喷!仅供参考和提供思路!!!!!!)
1 首先去官网下载海康的demo和文档,地址不知道?没关系 我给你啦!
https://open.hikvision.com/download/5cda567cf47ae80dd41a54b3?type=10
选择立即下载
2 选择java开发示例,然后选择预览回放和下载
官网的demo示例就在这啦 不过 要注意官方的提示文件 根据上面的注意事项 导入文件到你的项目中
(然后就可以开始进行Demo调试啦)
具体流程如下:
1 初始化设备 并定义一个list集合存储用户句柄
public static List lUserIDList = new ArrayList<>();//用户句柄集合 //初始化 private static void init() { //SDK初始化,一个程序只需要调用一次 boolean initSuc = hCNetSDK.NET_DVR_Init(); if (initSuc != true) { System.out.println("初始化失败"); } //异常消息回调 if (fExceptionCallBack == null) { fExceptionCallBack = new FExceptionCallBack_Imp(); } Pointer pUser = null; if (!hCNetSDK.NET_DVR_SetExceptionCallBack_V30(0, 0, fExceptionCallBack, pUser)) { return; } System.out.println("设置异常消息回调成功"); //启动SDK写日志 hCNetSDK.NET_DVR_SetLogToFile(3, "..\\sdkLog\\", false); }
2 登陆设备(只是演示和提供思路 具体实现请各位大佬根据实际业务)
登陆一台设备就会返回一个用户句柄也可称登陆句柄,后续调用预览是根据用户句柄来区分和管理的。
唯一注意的是,设备登陆后的返回值需要保存!登陆完后创建保存回调函数的音频数据文件
(注意:你的设备要和你自己的网络在同一个网段!!!!!!!查看电脑的网段 win+R 输入cmd 你的电脑是192.16.0段的 把摄像头的ip地址和网关也改成同段的 不然会报错误码7或错误码8等)
//登陆 private static void login(){ //存储登陆设备集合 List list = new ArrayList<>(); Device device = new Device(); device.setIp("192.168.1.16"); //改成自己设备的ip 用户名和密码 device.setUserName("admin"); device.setPassWord("Admin123"); Device devic1 = new Device(); device.setIp("192.168.1.17"); device.setUserName("admin"); device.setPassWord("Admin123"); list.add(device); list.add(devic1); //登录设备,每一台设备分别登录; 登录句柄是唯一的,可以区分设备 HCNetSDK.NET_DVR_USER_LOGIN_INFO m_strLoginInfo;//设备登录信息 HCNetSDK.NET_DVR_DEVICEINFO_V40 m_strDeviceInfo;//设备信息 for (Device d : list) { m_strLoginInfo = new HCNetSDK.NET_DVR_USER_LOGIN_INFO();//设备登录信息 m_strDeviceInfo = new HCNetSDK.NET_DVR_DEVICEINFO_V40();//设备信息 String m_sDeviceIP = d.getIp();//设备ip地址 m_strLoginInfo.sDeviceAddress = new byte[HCNetSDK.NET_DVR_DEV_ADDRESS_MAX_LEN]; System.arraycopy(m_sDeviceIP.getBytes(), 0, m_strLoginInfo.sDeviceAddress, 0, m_sDeviceIP.length()); String m_sUsername = d.getUserName();//设备用户名 m_strLoginInfo.sUserName = new byte[HCNetSDK.NET_DVR_LOGIN_USERNAME_MAX_LEN]; System.arraycopy(m_sUsername.getBytes(), 0, m_strLoginInfo.sUserName, 0, m_sUsername.length()); String m_sPassword = d.getPassWord();//设备密码 m_strLoginInfo.sPassword = new byte[HCNetSDK.NET_DVR_LOGIN_PASSWD_MAX_LEN]; System.arraycopy(m_sPassword.getBytes(), 0, m_strLoginInfo.sPassword, 0, m_sPassword.length()); m_strLoginInfo.wPort = 8000; //SDK端口 m_strLoginInfo.bUseAsynLogin = false; //是否异步登录:0- 否,1- 是 m_strLoginInfo.write(); lUserID = hCNetSDK.NET_DVR_Login_V40(m_strLoginInfo, m_strDeviceInfo); //将登陆返回的用户句柄保存!(这里很重要 是原先官网没有的,这里保存句柄是为了预览使用) lUserIDList.add(lUserID); if (lUserID==-1) { System.out.println("登录失败,错误码为" + hCNetSDK.NET_DVR_GetLastError()); } else { System.out.println("设备登录成功! " + "设备序列号:" + new String(m_strDeviceInfo.struDeviceV30.sSerialNumber).trim()); m_strDeviceInfo.read(); } //保存回调函数的音频数据 VideoDemo.setFile(lUserID); } }
3 创建保存回调函数的音频数据文件 同时创建一个map集合 保存对应的文件输出流 键为登陆后返回的用户句柄
//定义流的map集合 键为用户句柄(也就是你登陆返回的句柄) static Map outputStreamMap = new HashMap(); public static void setFile(int userId) { file = new File("/Download/" + new Date().getTime() + "(" + userId + ")" + ".mp4"); //保存回调函数的音频数据 if (!file.exists()) { try { file.createNewFile(); } catch (Exception e) { e.printStackTrace(); } }// FileOutputStream outputStream=new FileOutputStream(file); try { outputStreamMap.put(userId, new FileOutputStream(file)); } catch (FileNotFoundException e) { e.printStackTrace(); } }
4 线程run中执行预览代码 通过调用NET_DVR_RealPlay_V40实现预览
@Override public void run() { fRealDataCallBack = null; if (userId == -1) { System.out.println("请先注册"); return; } HCNetSDK.NET_DVR_PREVIEWINFO strClientInfo = new HCNetSDK.NET_DVR_PREVIEWINFO(); strClientInfo.read(); strClientInfo.hPlayWnd =null; //窗口句柄,从回调取流不显示一般设置为空 strClientInfo.lChannel = 1; //通道号 strClientInfo.dwStreamType = 0; //0-主码流,1-子码流,2-三码流,3-虚拟码流,以此类推 strClientInfo.dwLinkMode = 0; //连接方式:0- TCP方式,1- UDP方式,2- 多播方式,3- RTP方式,4- RTP/RTSP,5- RTP/HTTP,6- HRUDP(可靠传输) ,7- RTSP/HTTPS,8- NPQ strClientInfo.bBlocked = 1; strClientInfo.write(); if (fRealDataCallBack == null) { fRealDataCallBack = new FRealDataCallBack(); } //开启预览 lPlay = hCNetSDK.NET_DVR_RealPlay_V40(userId, strClientInfo, fRealDataCallBack, null); if (lPlay == -1) { int iErr = hCNetSDK.NET_DVR_GetLastError(); System.out.println("取流失败" + iErr); return; } System.out.println("取流成功"); }
5 修改回调函数 这里的回调函数参数都是sdk规定好的 在回调中取出集合中对应的流进行文件输出
static class FRealDataCallBack implements HCNetSDK.FRealDataCallBack_V30 { //预览回调 public void invoke(int lRealHandle, int dwDataType, ByteByReference pBuffer, int dwBufSize, Pointer pUser) {// System.out.println(lRealHandle + "码流数据回调" + pBuffer + ", 数据类型: " + dwDataType + ", 数据长度:" + dwBufSize + "puser:" + pUser); long offset = 0; ByteBuffer buffers = pBuffer.getPointer().getByteBuffer(offset, dwBufSize); byte[] bytes = new byte[dwBufSize]; buffers.rewind(); buffers.get(bytes); try { //根据lRealHandle从map中取出对应的流读取数据 outputStreamMap.get(lRealHandle).write(bytes); } catch (Exception e) { e.printStackTrace(); } } }
6 开启线程
public static void main(String[] args) throws InterruptedException { if (hCNetSDK == null && playControl == null) { if (!CreateSDKInstance()) { System.out.println("Load SDK fail"); return; } if (!CreatePlayInstance()) { System.out.println("Load PlayCtrl fail"); return; } } //linux系统建议调用以下接口加载组件库 if (osSelect.isLinux()) { HCNetSDK.BYTE_ARRAY ptrByteArray1 = new HCNetSDK.BYTE_ARRAY(256); HCNetSDK.BYTE_ARRAY ptrByteArray2 = new HCNetSDK.BYTE_ARRAY(256); //这里是库的绝对路径,请根据实际情况修改,注意改路径必须有访问权限 String strPath1 = System.getProperty("user.dir") + "/lib/libcrypto.so.1.1"; String strPath2 = System.getProperty("user.dir") + "/lib/libssl.so.1.1"; System.arraycopy(strPath1.getBytes(), 0, ptrByteArray1.byValue, 0, strPath1.length()); ptrByteArray1.write(); hCNetSDK.NET_DVR_SetSDKInitCfg(3, ptrByteArray1.getPointer()); System.arraycopy(strPath2.getBytes(), 0, ptrByteArray2.byValue, 0, strPath2.length()); ptrByteArray2.write(); hCNetSDK.NET_DVR_SetSDKInitCfg(4, ptrByteArray2.getPointer()); String strPathCom = System.getProperty("user.dir") + "/lib"; HCNetSDK.NET_DVR_LOCAL_SDK_PATH struComPath = new HCNetSDK.NET_DVR_LOCAL_SDK_PATH(); System.arraycopy(strPathCom.getBytes(), 0, struComPath.sPath, 0, strPathCom.length()); struComPath.write(); hCNetSDK.NET_DVR_SetSDKInitCfg(2, struComPath.getPointer()); } //初始化 init(); //登陆 login(); // 创建线程池对象指定线程数量 ExecutorService tp = Executors.newFixedThreadPool(2); VideoDemo video1=new VideoDemo(lUserIDList.get(0), 1); VideoDemo video2=new VideoDemo(lUserIDList.get(1),1) ; tp.submit(video1); tp.submit(video2); }
以上代码生成的视频在你盘符+Download 文件夹里面去找(如图所示)
注意:你现在生成的视频是无法直接查看的 需要用到海康的播放软件解码查看! 啥软件? 我把链接给你啦
https://www.hikvision.com/cn/support/Downloads/Desktop-Application/Hikvision-Player/
插件打开效果如图:
方案一 完整代码:
package com.NetSDKDemo;import Common.osSelect;import com.Device;import com.sun.jna.Native;import com.sun.jna.Pointer;import java.util.ArrayList;import java.util.List;import java.util.Timer;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;public class ClinetDemo { static HCNetSDK hCNetSDK = null; static PlayCtrl playControl = null; static int lUserID = 0;//用户句柄 public static List lUserIDList = new ArrayList<>();//用户句柄 static FExceptionCallBack_Imp fExceptionCallBack; static class FExceptionCallBack_Imp implements HCNetSDK.FExceptionCallBack { public void invoke(int dwType, int lUserID, int lHandle, Pointer pUser) { System.out.println("异常事件类型:" + dwType); return; } } public static void main(String[] args) throws InterruptedException { if (hCNetSDK == null && playControl == null) { if (!CreateSDKInstance()) { System.out.println("Load SDK fail"); return; } if (!CreatePlayInstance()) { System.out.println("Load PlayCtrl fail"); return; } } //linux系统建议调用以下接口加载组件库 if (osSelect.isLinux()) { HCNetSDK.BYTE_ARRAY ptrByteArray1 = new HCNetSDK.BYTE_ARRAY(256); HCNetSDK.BYTE_ARRAY ptrByteArray2 = new HCNetSDK.BYTE_ARRAY(256); //这里是库的绝对路径,请根据实际情况修改,注意改路径必须有访问权限 String strPath1 = System.getProperty("user.dir") + "/lib/libcrypto.so.1.1"; String strPath2 = System.getProperty("user.dir") + "/lib/libssl.so.1.1"; System.arraycopy(strPath1.getBytes(), 0, ptrByteArray1.byValue, 0, strPath1.length()); ptrByteArray1.write(); hCNetSDK.NET_DVR_SetSDKInitCfg(3, ptrByteArray1.getPointer()); System.arraycopy(strPath2.getBytes(), 0, ptrByteArray2.byValue, 0, strPath2.length()); ptrByteArray2.write(); hCNetSDK.NET_DVR_SetSDKInitCfg(4, ptrByteArray2.getPointer()); String strPathCom = System.getProperty("user.dir") + "/lib"; HCNetSDK.NET_DVR_LOCAL_SDK_PATH struComPath = new HCNetSDK.NET_DVR_LOCAL_SDK_PATH(); System.arraycopy(strPathCom.getBytes(), 0, struComPath.sPath, 0, strPathCom.length()); struComPath.write(); hCNetSDK.NET_DVR_SetSDKInitCfg(2, struComPath.getPointer()); } //初始化 init(); //登陆 login(); // 创建线程池对象指定线程数量 ExecutorService tp = Executors.newFixedThreadPool(2); VideoDemo video1=new VideoDemo(lUserIDList.get(0), 1); VideoDemo video2=new VideoDemo(lUserIDList.get(1),1) ; tp.submit(video1); tp.submit(video2); } //登陆 private static void login(){ //存储登陆设备集合 List list = new ArrayList<>(); Device device = new Device(); device.setIp("192.168.1.11"); device.setPassWord("admin"); device.setPassWord("123456"); Device device1 = new Device(); device1.setIp("192.168.1.12"); device1.setPassWord("admin"); device1.setPassWord("123456"); list.add(device); list.add(device1); //登录设备,每一台设备分别登录; 登录句柄是唯一的,可以区分设备 HCNetSDK.NET_DVR_USER_LOGIN_INFO m_strLoginInfo;//设备登录信息 HCNetSDK.NET_DVR_DEVICEINFO_V40 m_strDeviceInfo;//设备信息 for (Device d : list) { m_strLoginInfo = new HCNetSDK.NET_DVR_USER_LOGIN_INFO();//设备登录信息 m_strDeviceInfo = new HCNetSDK.NET_DVR_DEVICEINFO_V40();//设备信息 String m_sDeviceIP = d.getIp();//设备ip地址 m_strLoginInfo.sDeviceAddress = new byte[HCNetSDK.NET_DVR_DEV_ADDRESS_MAX_LEN]; System.arraycopy(m_sDeviceIP.getBytes(), 0, m_strLoginInfo.sDeviceAddress, 0, m_sDeviceIP.length()); String m_sUsername = d.getUserName();//设备用户名 m_strLoginInfo.sUserName = new byte[HCNetSDK.NET_DVR_LOGIN_USERNAME_MAX_LEN]; System.arraycopy(m_sUsername.getBytes(), 0, m_strLoginInfo.sUserName, 0, m_sUsername.length()); String m_sPassword = d.getPassWord();//设备密码 m_strLoginInfo.sPassword = new byte[HCNetSDK.NET_DVR_LOGIN_PASSWD_MAX_LEN]; System.arraycopy(m_sPassword.getBytes(), 0, m_strLoginInfo.sPassword, 0, m_sPassword.length()); m_strLoginInfo.wPort = 8000; //SDK端口 m_strLoginInfo.bUseAsynLogin = false; //是否异步登录:0- 否,1- 是 m_strLoginInfo.write(); lUserID = hCNetSDK.NET_DVR_Login_V40(m_strLoginInfo, m_strDeviceInfo); //将登陆返回的用户句柄保存!(这里很重要 是原先官网没有的,这里保存句柄是为了预览使用) lUserIDList.add(lUserID); if (lUserID==-1) { System.out.println("登录失败,错误码为" + hCNetSDK.NET_DVR_GetLastError()); } else { System.out.println("设备登录成功! " + "设备序列号:" + new String(m_strDeviceInfo.struDeviceV30.sSerialNumber).trim()); m_strDeviceInfo.read(); } //保存回调函数的音频数据 VideoDemo.setFile(lUserID); } } //初始化 private static void init() { //SDK初始化,一个程序只需要调用一次 boolean initSuc = hCNetSDK.NET_DVR_Init(); if (initSuc != true) { System.out.println("初始化失败"); } //异常消息回调 if (fExceptionCallBack == null) { fExceptionCallBack = new FExceptionCallBack_Imp(); } Pointer pUser = null; if (!hCNetSDK.NET_DVR_SetExceptionCallBack_V30(0, 0, fExceptionCallBack, pUser)) { return; } System.out.println("设置异常消息回调成功"); //启动SDK写日志 hCNetSDK.NET_DVR_SetLogToFile(3, "..\\sdkLog\\", false); } private static boolean CreateSDKInstance() { if (hCNetSDK == null) { synchronized (HCNetSDK.class) { String strDllPath = ""; try { if (osSelect.isWindows()) //win系统加载库路径 //strDllPath = System.getProperty("user.dir") + "\\lib\\HCNetSDK.dll"; strDllPath = "E:\\eclipse2019work\\ClientDemo-NetBeansPro\\HCNetSDK.dll"; else if (osSelect.isLinux()) //Linux系统加载库路径 // strDllPath = System.getProperty("user.dir") + "/lib/libhcnetsdk.so"; strDllPath = "/usr/local/lib/libhcnetsdk.so"; hCNetSDK = (HCNetSDK) Native.loadLibrary(strDllPath, HCNetSDK.class); } catch (Exception ex) { System.out.println("loadLibrary: " + strDllPath + " Error: " + ex.getMessage()); return false; } } } return true; } private static boolean CreatePlayInstance() { if (playControl == null) { synchronized (PlayCtrl.class) { String strPlayPath = ""; try { if (osSelect.isWindows()) //win系统加载库路径 strPlayPath = System.getProperty("user.dir") + "\\lib\\PlayCtrl.dll"; else if (osSelect.isLinux()) //Linux系统加载库路径 strPlayPath = System.getProperty("user.dir") + "/lib/libPlayCtrl.so"; playControl = (PlayCtrl) Native.loadLibrary("E:\\eclipse2019work\\ClientDemo-NetBeansPro\\PlayCtrl.dll", PlayCtrl.class); } catch (Exception ex) { System.out.println("loadLibrary: " + strPlayPath + " Error: " + ex.getMessage()); return false; } } } return true; } //注销设备 public void videoWrite() { //退出程序时调用,每一台设备分别注销 for (int id : lUserIDList) { if (hCNetSDK.NET_DVR_Logout(id)) { System.out.println("注销成功"); } } lUserIDList.clear(); //SDK反初始化,释放资源,只需要退出时调用一次 hCNetSDK.NET_DVR_Cleanup(); VideoDemo.outputStreamMap.clear(); }}
package com.NetSDKDemo;import com.sun.jna.Pointer;import com.sun.jna.ptr.ByteByReference;import com.sun.jna.ptr.IntByReference;import java.io.*;import java.lang.reflect.Parameter;import java.nio.ByteBuffer;import java.text.SimpleDateFormat;import java.util.Date;import java.util.HashMap;import java.util.Map;import java.util.Timer;import static com.NetSDKDemo.ClinetDemo.hCNetSDK;import static com.NetSDKDemo.ClinetDemo.playControl;public class VideoDemo implements Runnable{ Timer Downloadtimer;//下载用定时器 Timer Playbacktimer;//回放用定时器 static FRealDataCallBack fRealDataCallBack;//预览回调函数实现 //定义流的map集合 static Map outputStreamMap = new HashMap(); static int lPlay = -1; //预览句柄 static File file; private Integer userId; private Integer iChannelNo; public VideoDemo(Integer userId, Integer iChannelNo) { this.userId = userId; this.iChannelNo = iChannelNo; } @Override public void run() { fRealDataCallBack = null; if (userId == -1) { System.out.println("请先注册"); return; } HCNetSDK.NET_DVR_PREVIEWINFO strClientInfo = new HCNetSDK.NET_DVR_PREVIEWINFO(); strClientInfo.read(); strClientInfo.hPlayWnd = null; //窗口句柄,从回调取流不显示一般设置为空 strClientInfo.lChannel = iChannelNo; //通道号 strClientInfo.dwStreamType = 0; //0-主码流,1-子码流,2-三码流,3-虚拟码流,以此类推 strClientInfo.dwLinkMode = 0; //连接方式:0- TCP方式,1- UDP方式,2- 多播方式,3- RTP方式,4- RTP/RTSP,5- RTP/HTTP,6- HRUDP(可靠传输) ,7- RTSP/HTTPS,8- NPQ strClientInfo.bBlocked = 1; strClientInfo.write(); if (fRealDataCallBack == null) { fRealDataCallBack = new FRealDataCallBack(); } //开启预览 lPlay = hCNetSDK.NET_DVR_RealPlay_V40(userId, strClientInfo, fRealDataCallBack, null); if (lPlay == -1) { int iErr = hCNetSDK.NET_DVR_GetLastError(); System.out.println("取流失败" + iErr); return; } System.out.println("取流成功"); } //创建文件 public static void setFile(int userId) { file = new File("/Download/" + new Date().getTime() + "(" + userId + ")" + ".mp4"); //保存回调函数的音频数据 if (!file.exists()) { try { file.createNewFile(); } catch (Exception e) { e.printStackTrace(); } }// FileOutputStream outputStream=new FileOutputStream(file); try { outputStreamMap.put(userId, new FileOutputStream(file)); } catch (FileNotFoundException e) { e.printStackTrace(); } } static class FRealDataCallBack implements HCNetSDK.FRealDataCallBack_V30 { //预览回调 public void invoke(int lRealHandle, int dwDataType, ByteByReference pBuffer, int dwBufSize, Pointer pUser) { long offset = 0; ByteBuffer buffers = pBuffer.getPointer().getByteBuffer(offset, dwBufSize); byte[] bytes = new byte[dwBufSize]; buffers.rewind(); buffers.get(bytes); try { //从map中取出对应的流读取数据 outputStreamMap.get(lRealHandle).write(bytes); } catch (Exception e) { e.printStackTrace(); } } }}
实体类
package com;public class Device { private String ip; private String userName; private String passWord; public String getIp() { return ip; } public void setIp(String ip) { this.ip = ip; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getPassWord() { return passWord; } public void setPassWord(String passWord) { this.passWord = passWord; }}
小编的代码目录结构
简单总结:
1 初始化
2 登陆设备 并保存登陆设备的用户句柄
3 创建保存回调函数的音频文件
4 创建map集合 存储对应的文件输出流,键为用户句柄
5 对回调函数进行修改
(啊啊啊 博主给的代码有时候录制不同步!!!! 博主给的代码没有视频转码代码!!! 别急啦,往下个方案看看啦。注释就都在代码里了,这里就懒得写了哈)
改造后的代码(名字就叫TwoClientDemo)
public class TwoClientDemo { public static void main(String[] args) throws ExecutionException, InterruptedException { List devices = addDevice(); TwoVideoDemo video; CameraInfo cameraInfo; FutureTask ft; for (int i = 0; i < devices.size(); i++) { cameraInfo = new CameraInfo(); cameraInfo.setAddress(devices.get(i).getIp()); cameraInfo.setPort((short) 8000); cameraInfo.setUserName(devices.get(i).getUserName()); cameraInfo.setPwd(devices.get(i).getPassWord()); video = new TwoVideoDemo(cameraInfo); ft = new FutureTask<>(video); new Thread(ft).start(); //模拟录制过程 Thread.sleep(5000); ft.get(); System.out.println("取流成功"); }// renderSuccess(result);} //存储登陆设备集合 public static List addDevice() { List list = new ArrayList<>(); Device device = new Device(); device.setIp("192.168.1.16"); //改成自己设备的ip 用户名和密码 device.setUserName("admin"); device.setPassWord("Admin123"); list.add(device);// Device devic1 = new Device();// device.setIp("192.168.1.17");// device.setUserName("admin");// device.setPassWord("Admin123");// list.add(devic1); return list; }}
(值得注意的一点哈!!! 方案二自己改下这里的路径)
然后线程实现类 改改 回调函数啥也别写了 直接调海康sdk接口取流即可
线程实现类
public class TwoVideoDemo implements Callable { //初始化 public static final HCNetSDK INSTANCE = HCNetSDK.INSTANCE; static HCNetSDK sdk; private CameraInfo cameraInfo; public TwoVideoDemo(CameraInfo cameraInfo) { this.cameraInfo = cameraInfo; } @Override public Result call() throws Exception { sdk = INSTANCE; if (!sdk.NET_DVR_Init()) { System.out.println("初始化失败.................."); } //创建设备 HCNetSDK.NET_DVR_DEVICEINFO_V30 deInfo = new HCNetSDK.NET_DVR_DEVICEINFO_V30(); //注册用户设备 Integer id = sdk.NET_DVR_Login_V30(cameraInfo.getAddress(), cameraInfo.getPort(), cameraInfo.getUserName(), cameraInfo.getPwd(), deInfo); cameraInfo.setUserId(id); //判断是否注册成功 if (cameraInfo.getUserId().intValue() < 0) { System.out.println("注册设备失败 错误码为:"+sdk.NET_DVR_GetLastError()); } else { System.out.println("注册成功 Id为: " + cameraInfo.getUserId().intValue()); } //判断是否获取到设备能力 HCNetSDK.NET_DVR_WORKSTATE_V30 devWork = new HCNetSDK.NET_DVR_WORKSTATE_V30(); if (!sdk.NET_DVR_GetDVRWorkState_V30(cameraInfo.getUserId(), devWork)) { System.out.println("获取设备能力集失败,返回设备状态失败..............." + "错误码为:" + sdk.NET_DVR_GetLastError()); } //启动实时预览功能 创建clientInfo对象赋值预览参数 HCNetSDK.NET_DVR_CLIENTINFO clientInfo = new HCNetSDK.NET_DVR_CLIENTINFO(); clientInfo.lChannel = 1; //设置通道号 clientInfo.lLinkMode = 0; //TCP取流 clientInfo.sMultiCastIP = null; //不启动多播模式 //创建窗口句柄 clientInfo.hPlayWnd = null; FRealDataCallBack fRealDataCallBack = new FRealDataCallBack();//预览回调函数实现 while (true){ //开启实时预览 Integer key = sdk.NET_DVR_RealPlay_V30(cameraInfo.getUserId(), clientInfo, fRealDataCallBack, null, true); //判断是否预览成功 if (key.intValue() == -1) { sdk.NET_DVR_Logout(cameraInfo.getUserId()); sdk.NET_DVR_Cleanup(); System.out.println("预览失败 错误代码为: " + sdk.NET_DVR_GetLastError()); } System.out.println("开始预览成功");// 预览成功后 调用接口使视频资源保存到文件中 if (!sdk.NET_DVR_SaveRealData(key, "/Download/" + new Date().getTime() + ".mp4")) { sdk.NET_DVR_StopRealPlay(key); sdk.NET_DVR_Logout(cameraInfo.getUserId()); sdk.NET_DVR_Cleanup(); System.out.println("保存到文件失败 错误码为: " + sdk.NET_DVR_GetLastError()); } return Result.success("录制成功",null); } } class FRealDataCallBack implements HCNetSDK.FRealDataCallBack_V30 { @Override public void invoke(int lRealHandle, int dwDataType, ByteByReference pBuffer, int dwBufSize, Pointer pUser) { } } //停止录制 static void stopRecord(Integer i) { sdk.NET_DVR_Logout(i); sdk.NET_DVR_StopRealPlay(i); sdk.NET_DVR_Cleanup(); }}
Result(公共返回数据类)
package com.NetSDKDemo.comm;public class Result { private boolean flag; private Integer code; private String message; private Object data; public Result() { } public Result(boolean flag, Integer code, String message, Object data) { super(); this.flag = flag; this.code = code; this.message = message; this.data = data; } public Result(boolean flag, Integer code, String message) { super(); this.flag = flag; this.code = code; this.message = message; } public boolean isFlag() { return flag; } public void setFlag(boolean flag) { this.flag = flag; } public Integer getCode() { return code; } public void setCode(Integer code) { this.code = code; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } public Object getData() { return data; } public void setData(Object data) { this.data = data; } public static Result success(String msg, Object data) { return new Result(true, 200, msg, data); } public static Result error(String msg) { return new Result(false, 500, msg, null); } public static Result error(String msg, Object data,Integer code) { return new Result(true, code, msg, data); }}
CameraInfo实体类
public class CameraInfo { private String address; private String userName; private String pwd; private short port; private Integer userId; private Integer channel; private Integer key; public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getPwd() { return pwd; } public void setPwd(String pwd) { this.pwd = pwd; } public short getPort() { return port; } public void setPort(short port) { this.port = port; } public Integer getUserId() { return userId; } public void setUserId(Integer userId) { this.userId = userId; } public Integer getChannel() { return channel; } public void setChannel(Integer channel) { this.channel = channel; } public Integer getKey() { return key; } public void setKey(Integer key) { this.key = key; }}
结果
解码工具类
public class FormatConverterUtils { //视频解码配置 //linux下,路径肯定不是这样,是/root/xx这样子// public String outputPath = "/usr/local/tobaccocasedoc/Download/";// public String FFMPEG_PATH = "D:ffmpeg-master-latest-win64-gpl\\bin\\"; //linux中,路径为 /root/moch/ffmpeg-4.4-amd64-static/ 你自己在linux服务器上安装的路径 public static String FFMPEG_PATH = "/usr/local/ffmpeg/ffmpeg-git-20220910-amd64-static/"; //linux中,路径为 /root/moch/ffmpeg-4.4-amd64-static/ 你自己在linux服务器上安装的路径 public static void audioConverter(String resourcePath, String targetPath) { formatConverter(new File(resourcePath), new File(targetPath), false); } public static void videoConverter(String resourcePath, String targetPath) { formatConverter(new File(resourcePath), new File(targetPath), true); } public static void formatConverter(File fileInput, File fileOutPut, boolean isVideo) { fileInput.setExecutable(true);//设置可执行权限 fileInput.setReadable(true);//设置可读权限 fileInput.setWritable(true);//设置可写权限 fileOutPut.setExecutable(true);//设置可执行权限 fileOutPut.setReadable(true);//设置可读权限 fileOutPut.setWritable(true);//设置可写权限 if (null == fileInput || !fileInput.exists()) { throw new RuntimeException("源文件不存在,请检查源路径"); } if (null == fileOutPut) { throw new RuntimeException("转换后的路径为空,请检查转换后的存放路径是否正确"); } if (!fileOutPut.exists()) { try { fileOutPut.createNewFile(); } catch (IOException e) { System.out.println("转换时新建输出文件失败"); } } List commond = new ArrayList(); //输出直接覆盖文件 commond.add("-y"); commond.add("-i"); commond.add(fileInput.getAbsolutePath()); if (isVideo) { commond.add("-vcodec"); commond.add("libx264"); commond.add("-mbd"); commond.add("0"); commond.add("-c:a"); commond.add("aac"); commond.add("-s"); commond.add("720*720"); commond.add("-threads"); //指定同时启动线程执行数, 经测试到10再大速度几无变化 commond.add("25"); commond.add("-preset"); commond.add("ultrafast"); commond.add("-strict"); commond.add("-2"); commond.add("-pix_fmt"); commond.add("yuv420p"); commond.add("-movflags"); commond.add("faststart"); } commond.add(fileOutPut.getAbsolutePath()); //执行命令 executeCommand(commond); } public static String executeCommand(List commonds) { if (CollectionUtils.isEmpty(commonds)) { System.out.println("--- 指令执行失败,因为要执行的FFmpeg指令为空! ---"); return null; } LinkedList ffmpegCmds = new LinkedList<>(commonds); ffmpegCmds.addFirst(FFMPEG_PATH); // 设置ffmpeg程序所在路径 System.out.println("--- 待执行的FFmpeg指令为:---" + ffmpegCmds); Runtime runtime = Runtime.getRuntime(); Process ffmpeg = null; try { // 执行ffmpeg指令 ProcessBuilder builder = new ProcessBuilder("/bin/sh", "-c"); builder.command(ffmpegCmds); ffmpeg = builder.start(); System.out.println("--- 开始执行FFmpeg指令:--- 执行线程名:" + builder.toString()); // 取出输出流和错误流的信息 // 注意:必须要取出ffmpeg在执行命令过程中产生的输出信息,如果不取的话当输出流信息填满jvm存储输出留信息的缓冲区时,线程就回阻塞住 PrintStream errorStream = new PrintStream(ffmpeg.getErrorStream()); PrintStream inputStream = new PrintStream(ffmpeg.getInputStream()); errorStream.start(); inputStream.start(); // 等待ffmpeg命令执行完 ffmpeg.waitFor(); // 获取执行结果字符串 String result = errorStream.stringBuffer.append(inputStream.stringBuffer).toString(); // 输出执行的命令信息 String cmdStr = Arrays.toString(ffmpegCmds.toArray()).replace(",", ""); String resultStr = StringUtils.isBlank(result) ? "【异常】" : "正常"; System.out.println("--- 已执行的FFmepg命令: ---" + cmdStr + " 已执行完毕,执行结果: " + resultStr); return result; } catch (Exception e) { System.out.println("--- FFmpeg命令执行出错! --- 出错信息: " + e.getMessage()); return null; } finally { if (null != ffmpeg) { ProcessKiller ffmpegKiller = new ProcessKiller(ffmpeg); // JVM退出时,先通过钩子关闭FFmepg进程 runtime.addShutdownHook(ffmpegKiller); } } } static class PrintStream extends Thread { InputStream inputStream = null; BufferedReader bufferedReader = null; StringBuffer stringBuffer = new StringBuffer(); public PrintStream(InputStream inputStream) { this.inputStream = inputStream; } @Override public void run() { try { if (null == inputStream) { System.out.println("--- 读取输出流出错!因为当前输出流为空!---"); } bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); String line = null; while ((line = bufferedReader.readLine()) != null) { System.out.println(line); stringBuffer.append(line); } } catch (Exception e) { System.out.println("--- 读取输入流出错了!--- 错误信息:" + e.getMessage()); } finally { try { if (null != bufferedReader) { bufferedReader.close(); } if (null != inputStream) { inputStream.close(); } } catch (IOException e) { System.out.println("--- 调用PrintStream读取输出流后,关闭流时出错!---"); } } } } private static class ProcessKiller extends Thread { private Process process; public ProcessKiller(Process process) { this.process = process; } @Override public void run() { this.process.destroy(); System.out.println("--- 已销毁FFmpeg进程 --- 进程名: " + process.toString()); } } public static MultimediaInfo testMediaInfo(String path) throws EncoderException, MalformedURLException { MultimediaObject instance; if (path.startsWith("http")) { instance = new MultimediaObject(new URL(path)); } else { instance = new MultimediaObject(new File(path)); } return instance.getInfo(); } public static void testFFmpeg(String urlPath) { ProcessLocator processLocator = new DefaultFFMPEGLocator(); ProcessWrapper ffmpeg = processLocator.createExecutor(); ffmpeg.addArgument("-i"); ffmpeg.addArgument(urlPath); try { ffmpeg.execute(); String res = IOUtils.toString(ffmpeg.getErrorStream(), "UTF-8"); } catch (Exception e) { e.printStackTrace(); } finally { ffmpeg.destroy(); } } public static void codecToMp4(String sourceFile, String distFile, EncoderProgressListener pListener) throws EncoderException { File source = new File(sourceFile); File target = new File(distFile); if (target.exists()) { target.delete(); } AudioAttributes audioAttr = new AudioAttributes(); VideoAttributes videoAttr = new VideoAttributes(); EncodingAttributes encodingAttr = new EncodingAttributes(); audioAttr.setChannels(2); audioAttr.setCodec("aac"); audioAttr.setBitRate(128000); audioAttr.setSamplingRate(44100); videoAttr.setCodec("libx264"); videoAttr.setBitRate(2 * 1024 * 1024); videoAttr.setSize(new VideoSize(1080, 720)); videoAttr.setFaststart(true); videoAttr.setFrameRate(29); encodingAttr.setAudioAttributes(audioAttr); encodingAttr.setVideoAttributes(videoAttr); encodingAttr.setOutputFormat("mp4"); Encoder encoder = new Encoder(); encoder.encode(new MultimediaObject(source), target, encodingAttr, pListener); } public static void codecToMp4WithText(String sourceFile, String distFile, String textWaterMark, EncoderProgressListener pListener) throws EncoderException { File sourceVideo = new File(sourceFile); File target = new File(distFile); if (target.exists()) { target.delete(); } DrawtextFilter vf = new DrawtextFilter(textWaterMark, "(w-text_w)/2", "(h-text_h)/2", "宋体", 30.0, new Color("ffffff", "44")); vf.setShadow(new Color("000000", "44"), 2, 2); VideoAttributes videoAttributes = new VideoAttributes(); videoAttributes.addFilter(vf); EncodingAttributes attrs = new EncodingAttributes(); attrs.setVideoAttributes(videoAttributes); Encoder encoder = new Encoder(); encoder.encode(new MultimediaObject(sourceVideo), target, attrs, pListener); }// public static void main(String[] args) throws EncoderException, MalformedURLException { String videoPath = "D:\\usr\\local\\tobaccocasedoc\\Download\\test(0).mp4"; String wavPath = "D:\\usr\\local\\tobaccocasedoc\\Download\\test(0).mp4"; String mp3Path = "D:\\usr\\local\\tobaccocasedoc\\Download\\result.mp4"; //测试获取视频信息 MultimediaInfo info = testMediaInfo(videoPath); System.out.println(JSON.toJSONString(info)); //测试音频转码 codecToMp4(wavPath, mp3Path, new EncoderProgressListener() { @Override public void sourceInfo(MultimediaInfo info) { System.out.println(JSON.toJSONString(info)); } @Override public void progress(int permil) { System.out.println(permil); } @Override public void message(String message) { System.out.println(message); } }); }
(注: 因为官方的sdk不定时更新,一些接口参数也可能发生改变 大家可以根据返回的错误码判断错误信息 这篇文章仅供大家参考,如需要和我版本一致的sdk,源码下载类替换就好啦)
源码地址:(晚点上传)
对应的jar和maven需要大佬们自己去找找了
大佬勿喷!!!!!!!!!!!!!!!!!!!!! 写这个博客只是希望可以帮到大家
来源地址:https://blog.csdn.net/qq_46380138/article/details/126634069