当前位置:   article > 正文

Java SDK调用 海康网络摄像头 多摄像头同时预览 并取流_java对接海康摄像头

java对接海康摄像头

(修改前言:最近公司需求又对接海康的人脸设备,也就刚刚好有时间重新整理写的这篇博客,看评论也有很多人说有问题 demo无法跑通 小编在这呢 也重新测一遍 也会将一些细节更详细的列出来吗 同时呢  除了之前写的这套方案,小编在这也会再提供一个方案)

写在前面:

最近也遇到了调用海康多个摄像头实现同时预览的需求,不`过官方demo里并没详细的案例,上网查了下资料,也找不到对应的解决方案 ,电话咨询海康技术,没接过,信息没回过。这里就不对海康技术支持多作评价了,废话不多说。上方案!

小编首先整理了文档和获取流程(如图)

然后小编根据这个思路将SDK的Demo流程整个看了一遍,终于发现了问题的所在,接下来直接上代码(个人是根据海康提供的demo进行简单的修改,将部分公共代码给抽了出来,另外小编只用到了预览录制和停止录制功能,解码和其他功能小编没用到,所以小编在这里都干掉了。尽量能简洁易懂就简介易懂,大佬勿喷!仅供参考和提供思路!!!!!!)

1 首先去官网下载海康的demo和文档,地址不知道?没关系 我给你啦!

https://open.hikvision.com/download/5cda567cf47ae80dd41a54b3?type=10

选择立即下载

2  选择java开发示例,然后选择预览回放和下载

 官网的demo示例就在这啦 不过 要注意官方的提示文件 根据上面的注意事项 导入文件到你的项目中

(然后就可以开始进行Demo调试啦)

具体流程如下:

1 初始化设备 并定义一个list集合存储用户句柄 

  1. public static List<Integer> lUserIDList = new ArrayList<>();//用户句柄集合
  2. //初始化
  3. private static void init() {
  4. //SDK初始化,一个程序只需要调用一次
  5. boolean initSuc = hCNetSDK.NET_DVR_Init();
  6. if (initSuc != true) {
  7. System.out.println("初始化失败");
  8. }
  9. //异常消息回调
  10. if (fExceptionCallBack == null) {
  11. fExceptionCallBack = new FExceptionCallBack_Imp();
  12. }
  13. Pointer pUser = null;
  14. if (!hCNetSDK.NET_DVR_SetExceptionCallBack_V30(0, 0, fExceptionCallBack, pUser)) {
  15. return;
  16. }
  17. System.out.println("设置异常消息回调成功");
  18. //启动SDK写日志
  19. hCNetSDK.NET_DVR_SetLogToFile(3, "..\\sdkLog\\", false);
  20. }

 2 登陆设备(只是演示和提供思路 具体实现请各位大佬根据实际业务)

登陆一台设备就会返回一个用户句柄也可称登陆句柄,后续调用预览是根据用户句柄来区分和管理的。

唯一注意的是,设备登陆后的返回值需要保存!登陆完后创建保存回调函数的音频数据文件

(注意:你的设备要和你自己的网络在同一个网段!!!!!!!查看电脑的网段 win+R 输入cmd  你的电脑是192.16.0段的 把摄像头的ip地址和网关也改成同段的 不然会报错误码7或错误码8等)

  1. //登陆
  2. private static void login(){
  3. //存储登陆设备集合
  4. List<Device> list = new ArrayList<>();
  5. Device device = new Device();
  6. device.setIp("192.168.1.16"); //改成自己设备的ip 用户名和密码
  7. device.setUserName("admin");
  8. device.setPassWord("Admin123");
  9. Device devic1 = new Device();
  10. device.setIp("192.168.1.17");
  11. device.setUserName("admin");
  12. device.setPassWord("Admin123");
  13. list.add(device);
  14. list.add(devic1);
  15. //登录设备,每一台设备分别登录; 登录句柄是唯一的,可以区分设备
  16. HCNetSDK.NET_DVR_USER_LOGIN_INFO m_strLoginInfo;//设备登录信息
  17. HCNetSDK.NET_DVR_DEVICEINFO_V40 m_strDeviceInfo;//设备信息
  18. for (Device d : list) {
  19. m_strLoginInfo = new HCNetSDK.NET_DVR_USER_LOGIN_INFO();//设备登录信息
  20. m_strDeviceInfo = new HCNetSDK.NET_DVR_DEVICEINFO_V40();//设备信息
  21. String m_sDeviceIP = d.getIp();//设备ip地址
  22. m_strLoginInfo.sDeviceAddress = new byte[HCNetSDK.NET_DVR_DEV_ADDRESS_MAX_LEN];
  23. System.arraycopy(m_sDeviceIP.getBytes(), 0, m_strLoginInfo.sDeviceAddress, 0, m_sDeviceIP.length());
  24. String m_sUsername = d.getUserName();//设备用户名
  25. m_strLoginInfo.sUserName = new byte[HCNetSDK.NET_DVR_LOGIN_USERNAME_MAX_LEN];
  26. System.arraycopy(m_sUsername.getBytes(), 0, m_strLoginInfo.sUserName, 0, m_sUsername.length());
  27. String m_sPassword = d.getPassWord();//设备密码
  28. m_strLoginInfo.sPassword = new byte[HCNetSDK.NET_DVR_LOGIN_PASSWD_MAX_LEN];
  29. System.arraycopy(m_sPassword.getBytes(), 0, m_strLoginInfo.sPassword, 0, m_sPassword.length());
  30. m_strLoginInfo.wPort = 8000; //SDK端口
  31. m_strLoginInfo.bUseAsynLogin = false; //是否异步登录:0- 否,1- 是
  32. m_strLoginInfo.write();
  33. lUserID = hCNetSDK.NET_DVR_Login_V40(m_strLoginInfo, m_strDeviceInfo);
  34. //将登陆返回的用户句柄保存!(这里很重要 是原先官网没有的,这里保存句柄是为了预览使用)
  35. lUserIDList.add(lUserID);
  36. if (lUserID==-1) {
  37. System.out.println("登录失败,错误码为" + hCNetSDK.NET_DVR_GetLastError());
  38. } else {
  39. System.out.println("设备登录成功! " + "设备序列号:" + new String(m_strDeviceInfo.struDeviceV30.sSerialNumber).trim());
  40. m_strDeviceInfo.read();
  41. }
  42. //保存回调函数的音频数据
  43. VideoDemo.setFile(lUserID);
  44. }
  45. }

3 创建保存回调函数的音频数据文件  同时创建一个map集合 保存对应的文件输出流 键为登陆后返回的用户句柄

  1. //定义流的map集合 键为用户句柄(也就是你登陆返回的句柄)
  2. static Map<Integer, FileOutputStream> outputStreamMap = new HashMap();
  3. public static void setFile(int userId) {
  4. file = new File("/Download/" + new Date().getTime() + "(" + userId + ")" + ".mp4"); //保存回调函数的音频数据
  5. if (!file.exists()) {
  6. try {
  7. file.createNewFile();
  8. } catch (Exception e) {
  9. e.printStackTrace();
  10. }
  11. }
  12. // FileOutputStream outputStream=new FileOutputStream(file);
  13. try {
  14. outputStreamMap.put(userId, new FileOutputStream(file));
  15. } catch (FileNotFoundException e) {
  16. e.printStackTrace();
  17. }
  18. }

4 线程run中执行预览代码 通过调用NET_DVR_RealPlay_V40实现预览

  1. @Override
  2. public void run() {
  3. fRealDataCallBack = null;
  4. if (userId == -1) {
  5. System.out.println("请先注册");
  6. return;
  7. }
  8. HCNetSDK.NET_DVR_PREVIEWINFO strClientInfo = new HCNetSDK.NET_DVR_PREVIEWINFO();
  9. strClientInfo.read();
  10. strClientInfo.hPlayWnd =null; //窗口句柄,从回调取流不显示一般设置为空
  11. strClientInfo.lChannel = 1; //通道号
  12. strClientInfo.dwStreamType = 0; //0-主码流,1-子码流,2-三码流,3-虚拟码流,以此类推
  13. strClientInfo.dwLinkMode = 0; //连接方式:0- TCP方式,1- UDP方式,2- 多播方式,3- RTP方式,4- RTP/RTSP,5- RTP/HTTP,6- HRUDP(可靠传输) ,7- RTSP/HTTPS,8- NPQ
  14. strClientInfo.bBlocked = 1;
  15. strClientInfo.write();
  16. if (fRealDataCallBack == null) {
  17. fRealDataCallBack = new FRealDataCallBack();
  18. }
  19. //开启预览
  20. lPlay = hCNetSDK.NET_DVR_RealPlay_V40(userId, strClientInfo, fRealDataCallBack, null);
  21. if (lPlay == -1) {
  22. int iErr = hCNetSDK.NET_DVR_GetLastError();
  23. System.out.println("取流失败" + iErr);
  24. return;
  25. }
  26. System.out.println("取流成功");
  27. }

5 修改回调函数 这里的回调函数参数都是sdk规定好的 在回调中取出集合中对应的流进行文件输出

  1. static class FRealDataCallBack implements HCNetSDK.FRealDataCallBack_V30 {
  2. //预览回调
  3. public void invoke(int lRealHandle, int dwDataType, ByteByReference pBuffer, int dwBufSize, Pointer pUser) {
  4. // System.out.println(lRealHandle + "码流数据回调" + pBuffer + ", 数据类型: " + dwDataType + ", 数据长度:" + dwBufSize + "puser:" + pUser);
  5. long offset = 0;
  6. ByteBuffer buffers = pBuffer.getPointer().getByteBuffer(offset, dwBufSize);
  7. byte[] bytes = new byte[dwBufSize];
  8. buffers.rewind();
  9. buffers.get(bytes);
  10. try {
  11. //根据lRealHandle从map中取出对应的流读取数据
  12. outputStreamMap.get(lRealHandle).write(bytes);
  13. } catch (Exception e) {
  14. e.printStackTrace();
  15. }
  16. }
  17. }

6 开启线程 

  1. public static void main(String[] args) throws InterruptedException {
  2. if (hCNetSDK == null && playControl == null) {
  3. if (!CreateSDKInstance()) {
  4. System.out.println("Load SDK fail");
  5. return;
  6. }
  7. if (!CreatePlayInstance()) {
  8. System.out.println("Load PlayCtrl fail");
  9. return;
  10. }
  11. }
  12. //linux系统建议调用以下接口加载组件库
  13. if (osSelect.isLinux()) {
  14. HCNetSDK.BYTE_ARRAY ptrByteArray1 = new HCNetSDK.BYTE_ARRAY(256);
  15. HCNetSDK.BYTE_ARRAY ptrByteArray2 = new HCNetSDK.BYTE_ARRAY(256);
  16. //这里是库的绝对路径,请根据实际情况修改,注意改路径必须有访问权限
  17. String strPath1 = System.getProperty("user.dir") + "/lib/libcrypto.so.1.1";
  18. String strPath2 = System.getProperty("user.dir") + "/lib/libssl.so.1.1";
  19. System.arraycopy(strPath1.getBytes(), 0, ptrByteArray1.byValue, 0, strPath1.length());
  20. ptrByteArray1.write();
  21. hCNetSDK.NET_DVR_SetSDKInitCfg(3, ptrByteArray1.getPointer());
  22. System.arraycopy(strPath2.getBytes(), 0, ptrByteArray2.byValue, 0, strPath2.length());
  23. ptrByteArray2.write();
  24. hCNetSDK.NET_DVR_SetSDKInitCfg(4, ptrByteArray2.getPointer());
  25. String strPathCom = System.getProperty("user.dir") + "/lib";
  26. HCNetSDK.NET_DVR_LOCAL_SDK_PATH struComPath = new HCNetSDK.NET_DVR_LOCAL_SDK_PATH();
  27. System.arraycopy(strPathCom.getBytes(), 0, struComPath.sPath, 0, strPathCom.length());
  28. struComPath.write();
  29. hCNetSDK.NET_DVR_SetSDKInitCfg(2, struComPath.getPointer());
  30. }
  31. //初始化
  32. init();
  33. //登陆
  34. login();
  35. // 创建线程池对象指定线程数量
  36. ExecutorService tp = Executors.newFixedThreadPool(2);
  37. VideoDemo video1=new VideoDemo(lUserIDList.get(0), 1);
  38. VideoDemo video2=new VideoDemo(lUserIDList.get(1),1) ;
  39. tp.submit(video1);
  40. tp.submit(video2);
  41. }

以上代码生成的视频在你盘符+Download 文件夹里面去找(如图所示) 

 注意:你现在生成的视频是无法直接查看的 需要用到海康的播放软件解码查看! 啥软件? 我把链接给你啦

https://www.hikvision.com/cn/support/Downloads/Desktop-Application/Hikvision-Player/

插件打开效果如图:

方案一 完整代码:

  1. package com.NetSDKDemo;
  2. import Common.osSelect;
  3. import com.Device;
  4. import com.sun.jna.Native;
  5. import com.sun.jna.Pointer;
  6. import java.util.ArrayList;
  7. import java.util.List;
  8. import java.util.Timer;
  9. import java.util.concurrent.ExecutorService;
  10. import java.util.concurrent.Executors;
  11. /**
  12. * @create 2020-12-24-17:55
  13. */
  14. public class ClinetDemo {
  15. static HCNetSDK hCNetSDK = null;
  16. static PlayCtrl playControl = null;
  17. static int lUserID = 0;//用户句柄
  18. public static List<Integer> lUserIDList = new ArrayList<>();//用户句柄
  19. static FExceptionCallBack_Imp fExceptionCallBack;
  20. static class FExceptionCallBack_Imp implements HCNetSDK.FExceptionCallBack {
  21. public void invoke(int dwType, int lUserID, int lHandle, Pointer pUser) {
  22. System.out.println("异常事件类型:" + dwType);
  23. return;
  24. }
  25. }
  26. public static void main(String[] args) throws InterruptedException {
  27. if (hCNetSDK == null && playControl == null) {
  28. if (!CreateSDKInstance()) {
  29. System.out.println("Load SDK fail");
  30. return;
  31. }
  32. if (!CreatePlayInstance()) {
  33. System.out.println("Load PlayCtrl fail");
  34. return;
  35. }
  36. }
  37. //linux系统建议调用以下接口加载组件库
  38. if (osSelect.isLinux()) {
  39. HCNetSDK.BYTE_ARRAY ptrByteArray1 = new HCNetSDK.BYTE_ARRAY(256);
  40. HCNetSDK.BYTE_ARRAY ptrByteArray2 = new HCNetSDK.BYTE_ARRAY(256);
  41. //这里是库的绝对路径,请根据实际情况修改,注意改路径必须有访问权限
  42. String strPath1 = System.getProperty("user.dir") + "/lib/libcrypto.so.1.1";
  43. String strPath2 = System.getProperty("user.dir") + "/lib/libssl.so.1.1";
  44. System.arraycopy(strPath1.getBytes(), 0, ptrByteArray1.byValue, 0, strPath1.length());
  45. ptrByteArray1.write();
  46. hCNetSDK.NET_DVR_SetSDKInitCfg(3, ptrByteArray1.getPointer());
  47. System.arraycopy(strPath2.getBytes(), 0, ptrByteArray2.byValue, 0, strPath2.length());
  48. ptrByteArray2.write();
  49. hCNetSDK.NET_DVR_SetSDKInitCfg(4, ptrByteArray2.getPointer());
  50. String strPathCom = System.getProperty("user.dir") + "/lib";
  51. HCNetSDK.NET_DVR_LOCAL_SDK_PATH struComPath = new HCNetSDK.NET_DVR_LOCAL_SDK_PATH();
  52. System.arraycopy(strPathCom.getBytes(), 0, struComPath.sPath, 0, strPathCom.length());
  53. struComPath.write();
  54. hCNetSDK.NET_DVR_SetSDKInitCfg(2, struComPath.getPointer());
  55. }
  56. //初始化
  57. init();
  58. //登陆
  59. login();
  60. // 创建线程池对象指定线程数量
  61. ExecutorService tp = Executors.newFixedThreadPool(2);
  62. VideoDemo video1=new VideoDemo(lUserIDList.get(0), 1);
  63. VideoDemo video2=new VideoDemo(lUserIDList.get(1),1) ;
  64. tp.submit(video1);
  65. tp.submit(video2);
  66. }
  67. //登陆
  68. private static void login(){
  69. //存储登陆设备集合
  70. List<Device> list = new ArrayList<>();
  71. Device device = new Device();
  72. device.setIp("192.168.1.11");
  73. device.setPassWord("admin");
  74. device.setPassWord("123456");
  75. Device device1 = new Device();
  76. device1.setIp("192.168.1.12");
  77. device1.setPassWord("admin");
  78. device1.setPassWord("123456");
  79. list.add(device);
  80. list.add(device1);
  81. //登录设备,每一台设备分别登录; 登录句柄是唯一的,可以区分设备
  82. HCNetSDK.NET_DVR_USER_LOGIN_INFO m_strLoginInfo;//设备登录信息
  83. HCNetSDK.NET_DVR_DEVICEINFO_V40 m_strDeviceInfo;//设备信息
  84. for (Device d : list) {
  85. m_strLoginInfo = new HCNetSDK.NET_DVR_USER_LOGIN_INFO();//设备登录信息
  86. m_strDeviceInfo = new HCNetSDK.NET_DVR_DEVICEINFO_V40();//设备信息
  87. String m_sDeviceIP = d.getIp();//设备ip地址
  88. m_strLoginInfo.sDeviceAddress = new byte[HCNetSDK.NET_DVR_DEV_ADDRESS_MAX_LEN];
  89. System.arraycopy(m_sDeviceIP.getBytes(), 0, m_strLoginInfo.sDeviceAddress, 0, m_sDeviceIP.length());
  90. String m_sUsername = d.getUserName();//设备用户名
  91. m_strLoginInfo.sUserName = new byte[HCNetSDK.NET_DVR_LOGIN_USERNAME_MAX_LEN];
  92. System.arraycopy(m_sUsername.getBytes(), 0, m_strLoginInfo.sUserName, 0, m_sUsername.length());
  93. String m_sPassword = d.getPassWord();//设备密码
  94. m_strLoginInfo.sPassword = new byte[HCNetSDK.NET_DVR_LOGIN_PASSWD_MAX_LEN];
  95. System.arraycopy(m_sPassword.getBytes(), 0, m_strLoginInfo.sPassword, 0, m_sPassword.length());
  96. m_strLoginInfo.wPort = 8000; //SDK端口
  97. m_strLoginInfo.bUseAsynLogin = false; //是否异步登录:0- 否,1- 是
  98. m_strLoginInfo.write();
  99. lUserID = hCNetSDK.NET_DVR_Login_V40(m_strLoginInfo, m_strDeviceInfo);
  100. //将登陆返回的用户句柄保存!(这里很重要 是原先官网没有的,这里保存句柄是为了预览使用)
  101. lUserIDList.add(lUserID);
  102. if (lUserID==-1) {
  103. System.out.println("登录失败,错误码为" + hCNetSDK.NET_DVR_GetLastError());
  104. } else {
  105. System.out.println("设备登录成功! " + "设备序列号:" + new String(m_strDeviceInfo.struDeviceV30.sSerialNumber).trim());
  106. m_strDeviceInfo.read();
  107. }
  108. //保存回调函数的音频数据
  109. VideoDemo.setFile(lUserID);
  110. }
  111. }
  112. //初始化
  113. private static void init() {
  114. //SDK初始化,一个程序只需要调用一次
  115. boolean initSuc = hCNetSDK.NET_DVR_Init();
  116. if (initSuc != true) {
  117. System.out.println("初始化失败");
  118. }
  119. //异常消息回调
  120. if (fExceptionCallBack == null) {
  121. fExceptionCallBack = new FExceptionCallBack_Imp();
  122. }
  123. Pointer pUser = null;
  124. if (!hCNetSDK.NET_DVR_SetExceptionCallBack_V30(0, 0, fExceptionCallBack, pUser)) {
  125. return;
  126. }
  127. System.out.println("设置异常消息回调成功");
  128. //启动SDK写日志
  129. hCNetSDK.NET_DVR_SetLogToFile(3, "..\\sdkLog\\", false);
  130. }
  131. /**
  132. * 动态库加载
  133. *
  134. * @return
  135. */
  136. private static boolean CreateSDKInstance() {
  137. if (hCNetSDK == null) {
  138. synchronized (HCNetSDK.class) {
  139. String strDllPath = "";
  140. try {
  141. if (osSelect.isWindows())
  142. //win系统加载库路径
  143. //strDllPath = System.getProperty("user.dir") + "\\lib\\HCNetSDK.dll";
  144. strDllPath = "E:\\eclipse2019work\\ClientDemo-NetBeansPro\\HCNetSDK.dll";
  145. else if (osSelect.isLinux())
  146. //Linux系统加载库路径
  147. // strDllPath = System.getProperty("user.dir") + "/lib/libhcnetsdk.so";
  148. strDllPath = "/usr/local/lib/libhcnetsdk.so";
  149. hCNetSDK = (HCNetSDK) Native.loadLibrary(strDllPath, HCNetSDK.class);
  150. } catch (Exception ex) {
  151. System.out.println("loadLibrary: " + strDllPath + " Error: " + ex.getMessage());
  152. return false;
  153. }
  154. }
  155. }
  156. return true;
  157. }
  158. /**
  159. * 播放库加载
  160. *
  161. * @return
  162. */
  163. private static boolean CreatePlayInstance() {
  164. if (playControl == null) {
  165. synchronized (PlayCtrl.class) {
  166. String strPlayPath = "";
  167. try {
  168. if (osSelect.isWindows())
  169. //win系统加载库路径
  170. strPlayPath = System.getProperty("user.dir") + "\\lib\\PlayCtrl.dll";
  171. else if (osSelect.isLinux())
  172. //Linux系统加载库路径
  173. strPlayPath = System.getProperty("user.dir") + "/lib/libPlayCtrl.so";
  174. playControl = (PlayCtrl) Native.loadLibrary("E:\\eclipse2019work\\ClientDemo-NetBeansPro\\PlayCtrl.dll", PlayCtrl.class);
  175. } catch (Exception ex) {
  176. System.out.println("loadLibrary: " + strPlayPath + " Error: " + ex.getMessage());
  177. return false;
  178. }
  179. }
  180. }
  181. return true;
  182. }
  183. //注销设备
  184. public void videoWrite() {
  185. //退出程序时调用,每一台设备分别注销
  186. for (int id : lUserIDList) {
  187. if (hCNetSDK.NET_DVR_Logout(id)) {
  188. System.out.println("注销成功");
  189. }
  190. }
  191. lUserIDList.clear();
  192. //SDK反初始化,释放资源,只需要退出时调用一次
  193. hCNetSDK.NET_DVR_Cleanup();
  194. VideoDemo.outputStreamMap.clear();
  195. }
  196. }
  1. package com.NetSDKDemo;
  2. import com.sun.jna.Pointer;
  3. import com.sun.jna.ptr.ByteByReference;
  4. import com.sun.jna.ptr.IntByReference;
  5. import java.io.*;
  6. import java.lang.reflect.Parameter;
  7. import java.nio.ByteBuffer;
  8. import java.text.SimpleDateFormat;
  9. import java.util.Date;
  10. import java.util.HashMap;
  11. import java.util.Map;
  12. import java.util.Timer;
  13. import static com.NetSDKDemo.ClinetDemo.hCNetSDK;
  14. import static com.NetSDKDemo.ClinetDemo.playControl;
  15. /**
  16. * 视频取流预览,下载,抓图
  17. *
  18. * @create 2022-03-30-9:48
  19. */
  20. public class VideoDemo implements Runnable{
  21. Timer Downloadtimer;//下载用定时器
  22. Timer Playbacktimer;//回放用定时器
  23. static FRealDataCallBack fRealDataCallBack;//预览回调函数实现
  24. //定义流的map集合
  25. static Map<Integer, FileOutputStream> outputStreamMap = new HashMap();
  26. static int lPlay = -1; //预览句柄
  27. static File file;
  28. private Integer userId;
  29. private Integer iChannelNo;
  30. public VideoDemo(Integer userId, Integer iChannelNo) {
  31. this.userId = userId;
  32. this.iChannelNo = iChannelNo;
  33. }
  34. @Override
  35. public void run() {
  36. fRealDataCallBack = null;
  37. if (userId == -1) {
  38. System.out.println("请先注册");
  39. return;
  40. }
  41. HCNetSDK.NET_DVR_PREVIEWINFO strClientInfo = new HCNetSDK.NET_DVR_PREVIEWINFO();
  42. strClientInfo.read();
  43. strClientInfo.hPlayWnd = null; //窗口句柄,从回调取流不显示一般设置为空
  44. strClientInfo.lChannel = iChannelNo; //通道号
  45. strClientInfo.dwStreamType = 0; //0-主码流,1-子码流,2-三码流,3-虚拟码流,以此类推
  46. strClientInfo.dwLinkMode = 0; //连接方式:0- TCP方式,1- UDP方式,2- 多播方式,3- RTP方式,4- RTP/RTSP,5- RTP/HTTP,6- HRUDP(可靠传输) ,7- RTSP/HTTPS,8- NPQ
  47. strClientInfo.bBlocked = 1;
  48. strClientInfo.write();
  49. if (fRealDataCallBack == null) {
  50. fRealDataCallBack = new FRealDataCallBack();
  51. }
  52. //开启预览
  53. lPlay = hCNetSDK.NET_DVR_RealPlay_V40(userId, strClientInfo, fRealDataCallBack, null);
  54. if (lPlay == -1) {
  55. int iErr = hCNetSDK.NET_DVR_GetLastError();
  56. System.out.println("取流失败" + iErr);
  57. return;
  58. }
  59. System.out.println("取流成功");
  60. }
  61. //创建文件
  62. /**
  63. *
  64. *
  65. * @date 2022/8/31 23:37
  66. * @param userId:登陆返回的用户句柄
  67. */
  68. public static void setFile(int userId) {
  69. file = new File("/Download/" + new Date().getTime() + "(" + userId + ")" + ".mp4"); //保存回调函数的音频数据
  70. if (!file.exists()) {
  71. try {
  72. file.createNewFile();
  73. } catch (Exception e) {
  74. e.printStackTrace();
  75. }
  76. }
  77. // FileOutputStream outputStream=new FileOutputStream(file);
  78. try {
  79. outputStreamMap.put(userId, new FileOutputStream(file));
  80. } catch (FileNotFoundException e) {
  81. e.printStackTrace();
  82. }
  83. }
  84. static class FRealDataCallBack implements HCNetSDK.FRealDataCallBack_V30 {
  85. //预览回调
  86. public void invoke(int lRealHandle, int dwDataType, ByteByReference pBuffer, int dwBufSize, Pointer pUser) {
  87. long offset = 0;
  88. ByteBuffer buffers = pBuffer.getPointer().getByteBuffer(offset, dwBufSize);
  89. byte[] bytes = new byte[dwBufSize];
  90. buffers.rewind();
  91. buffers.get(bytes);
  92. try {
  93. //从map中取出对应的流读取数据
  94. outputStreamMap.get(lRealHandle).write(bytes);
  95. } catch (Exception e) {
  96. e.printStackTrace();
  97. }
  98. }
  99. }
  100. }

实体类

  1. package com;
  2. /**
  3. * @author lws
  4. * @date 2022/8/31 23:07
  5. */
  6. public class Device {
  7. private String ip;
  8. private String userName;
  9. private String passWord;
  10. public String getIp() {
  11. return ip;
  12. }
  13. public void setIp(String ip) {
  14. this.ip = ip;
  15. }
  16. public String getUserName() {
  17. return userName;
  18. }
  19. public void setUserName(String userName) {
  20. this.userName = userName;
  21. }
  22. public String getPassWord() {
  23. return passWord;
  24. }
  25. public void setPassWord(String passWord) {
  26. this.passWord = passWord;
  27. }
  28. }

小编的代码目录结构

简单总结:

1 初始化

2 登陆设备 并保存登陆设备的用户句柄

3 创建保存回调函数的音频文件

4 创建map集合 存储对应的文件输出流,键为用户句柄

5 对回调函数进行修改 

(啊啊啊 博主给的代码有时候录制不同步!!!! 博主给的代码没有视频转码代码!!! 别急啦,往下个方案看看啦。注释就都在代码里了,这里就懒得写了哈)

改造后的代码(名字就叫TwoClientDemo)

  1. public class TwoClientDemo {
  2. public static void main(String[] args) throws ExecutionException, InterruptedException {
  3. List<Device> devices = addDevice();
  4. TwoVideoDemo video;
  5. CameraInfo cameraInfo;
  6. FutureTask<Result> ft;
  7. for (int i = 0; i < devices.size(); i++) {
  8. cameraInfo = new CameraInfo();
  9. cameraInfo.setAddress(devices.get(i).getIp());
  10. cameraInfo.setPort((short) 8000);
  11. cameraInfo.setUserName(devices.get(i).getUserName());
  12. cameraInfo.setPwd(devices.get(i).getPassWord());
  13. video = new TwoVideoDemo(cameraInfo);
  14. ft = new FutureTask<>(video);
  15. new Thread(ft).start();
  16. //模拟录制过程
  17. Thread.sleep(5000);
  18. ft.get();
  19. System.out.println("取流成功");
  20. }
  21. // renderSuccess(result);
  22. }
  23. //存储登陆设备集合
  24. public static List<Device> addDevice() {
  25. List<Device> list = new ArrayList<>();
  26. Device device = new Device();
  27. device.setIp("192.168.1.16"); //改成自己设备的ip 用户名和密码
  28. device.setUserName("admin");
  29. device.setPassWord("Admin123");
  30. list.add(device);
  31. // Device devic1 = new Device();
  32. // device.setIp("192.168.1.17");
  33. // device.setUserName("admin");
  34. // device.setPassWord("Admin123");
  35. // list.add(devic1);
  36. return list;
  37. }
  38. }

(值得注意的一点哈!!! 方案二自己改下这里的路径) 

然后线程实现类 改改 回调函数啥也别写了 直接调海康sdk接口取流即可 

线程实现类

  1. public class TwoVideoDemo implements Callable<Result> {
  2. //初始化
  3. public static final HCNetSDK INSTANCE = HCNetSDK.INSTANCE;
  4. static HCNetSDK sdk;
  5. private CameraInfo cameraInfo;
  6. public TwoVideoDemo(CameraInfo cameraInfo) {
  7. this.cameraInfo = cameraInfo;
  8. }
  9. @Override
  10. public Result call() throws Exception {
  11. sdk = INSTANCE;
  12. if (!sdk.NET_DVR_Init()) {
  13. System.out.println("初始化失败..................");
  14. }
  15. //创建设备
  16. HCNetSDK.NET_DVR_DEVICEINFO_V30 deInfo = new HCNetSDK.NET_DVR_DEVICEINFO_V30();
  17. //注册用户设备
  18. Integer id = sdk.NET_DVR_Login_V30(cameraInfo.getAddress(), cameraInfo.getPort(),
  19. cameraInfo.getUserName(), cameraInfo.getPwd(), deInfo);
  20. cameraInfo.setUserId(id);
  21. //判断是否注册成功
  22. if (cameraInfo.getUserId().intValue() < 0) {
  23. System.out.println("注册设备失败 错误码为:"+sdk.NET_DVR_GetLastError());
  24. } else {
  25. System.out.println("注册成功 Id为: " + cameraInfo.getUserId().intValue());
  26. }
  27. //判断是否获取到设备能力
  28. HCNetSDK.NET_DVR_WORKSTATE_V30 devWork = new HCNetSDK.NET_DVR_WORKSTATE_V30();
  29. if (!sdk.NET_DVR_GetDVRWorkState_V30(cameraInfo.getUserId(), devWork)) {
  30. System.out.println("获取设备能力集失败,返回设备状态失败..............." + "错误码为:" + sdk.NET_DVR_GetLastError());
  31. }
  32. //启动实时预览功能 创建clientInfo对象赋值预览参数
  33. HCNetSDK.NET_DVR_CLIENTINFO clientInfo = new HCNetSDK.NET_DVR_CLIENTINFO();
  34. clientInfo.lChannel = 1; //设置通道号
  35. clientInfo.lLinkMode = 0; //TCP取流
  36. clientInfo.sMultiCastIP = null; //不启动多播模式
  37. //创建窗口句柄
  38. clientInfo.hPlayWnd = null;
  39. FRealDataCallBack fRealDataCallBack = new FRealDataCallBack();//预览回调函数实现
  40. while (true){
  41. //开启实时预览
  42. Integer key = sdk.NET_DVR_RealPlay_V30(cameraInfo.getUserId(), clientInfo, fRealDataCallBack, null, true);
  43. //判断是否预览成功
  44. if (key.intValue() == -1) {
  45. sdk.NET_DVR_Logout(cameraInfo.getUserId());
  46. sdk.NET_DVR_Cleanup();
  47. System.out.println("预览失败 错误代码为: " + sdk.NET_DVR_GetLastError());
  48. }
  49. System.out.println("开始预览成功");
  50. // 预览成功后 调用接口使视频资源保存到文件中
  51. if (!sdk.NET_DVR_SaveRealData(key, "/Download/" + new Date().getTime() + ".mp4")) {
  52. sdk.NET_DVR_StopRealPlay(key);
  53. sdk.NET_DVR_Logout(cameraInfo.getUserId());
  54. sdk.NET_DVR_Cleanup();
  55. System.out.println("保存到文件失败 错误码为: " + sdk.NET_DVR_GetLastError());
  56. }
  57. return Result.success("录制成功",null);
  58. }
  59. }
  60. /**
  61. * @param预览回调接口实现类
  62. */
  63. class FRealDataCallBack implements HCNetSDK.FRealDataCallBack_V30 {
  64. @Override
  65. public void invoke(int lRealHandle, int dwDataType, ByteByReference pBuffer, int dwBufSize, Pointer pUser) {
  66. }
  67. }
  68. //停止录制
  69. static void stopRecord(Integer i) {
  70. sdk.NET_DVR_Logout(i);
  71. sdk.NET_DVR_StopRealPlay(i);
  72. sdk.NET_DVR_Cleanup();
  73. }
  74. }

Result(公共返回数据类)

  1. package com.NetSDKDemo.comm;
  2. public class Result {
  3. private boolean flag;
  4. private Integer code;
  5. private String message;
  6. private Object data;
  7. public Result() {
  8. }
  9. public Result(boolean flag, Integer code, String message, Object data) {
  10. super();
  11. this.flag = flag;
  12. this.code = code;
  13. this.message = message;
  14. this.data = data;
  15. }
  16. public Result(boolean flag, Integer code, String message) {
  17. super();
  18. this.flag = flag;
  19. this.code = code;
  20. this.message = message;
  21. }
  22. public boolean isFlag() {
  23. return flag;
  24. }
  25. public void setFlag(boolean flag) {
  26. this.flag = flag;
  27. }
  28. public Integer getCode() {
  29. return code;
  30. }
  31. public void setCode(Integer code) {
  32. this.code = code;
  33. }
  34. public String getMessage() {
  35. return message;
  36. }
  37. public void setMessage(String message) {
  38. this.message = message;
  39. }
  40. public Object getData() {
  41. return data;
  42. }
  43. public void setData(Object data) {
  44. this.data = data;
  45. }
  46. public static Result success(String msg, Object data) {
  47. return new Result(true, 200, msg, data);
  48. }
  49. public static Result error(String msg) {
  50. return new Result(false, 500, msg, null);
  51. }
  52. public static Result error(String msg, Object data,Integer code) {
  53. return new Result(true, code, msg, data);
  54. }
  55. }
CameraInfo实体类
  1. public class CameraInfo {
  2. private String address;
  3. private String userName;
  4. private String pwd;
  5. private short port;
  6. private Integer userId;
  7. private Integer channel;
  8. private Integer key;
  9. public String getAddress() {
  10. return address;
  11. }
  12. public void setAddress(String address) {
  13. this.address = address;
  14. }
  15. public String getUserName() {
  16. return userName;
  17. }
  18. public void setUserName(String userName) {
  19. this.userName = userName;
  20. }
  21. public String getPwd() {
  22. return pwd;
  23. }
  24. public void setPwd(String pwd) {
  25. this.pwd = pwd;
  26. }
  27. public short getPort() {
  28. return port;
  29. }
  30. public void setPort(short port) {
  31. this.port = port;
  32. }
  33. public Integer getUserId() {
  34. return userId;
  35. }
  36. public void setUserId(Integer userId) {
  37. this.userId = userId;
  38. }
  39. public Integer getChannel() {
  40. return channel;
  41. }
  42. public void setChannel(Integer channel) {
  43. this.channel = channel;
  44. }
  45. public Integer getKey() {
  46. return key;
  47. }
  48. public void setKey(Integer key) {
  49. this.key = key;
  50. }
  51. }

结果

解码工具类

  1. public class FormatConverterUtils {
  2. /**
  3. * FFmpeg程序执行路径
  4. * 当前系统安装好ffmpeg程序并配置好相应的环境变量后,值为ffmpeg.exe可执行程序文件在实际系统中的绝对路径
  5. */
  6. //视频解码配置
  7. //linux下,路径肯定不是这样,是/root/xx这样子
  8. // public String outputPath = "/usr/local/tobaccocasedoc/Download/";
  9. // public String FFMPEG_PATH = "D:ffmpeg-master-latest-win64-gpl\\bin\\"; //linux中,路径为 /root/moch/ffmpeg-4.4-amd64-static/ 你自己在linux服务器上安装的路径
  10. public static String FFMPEG_PATH = "/usr/local/ffmpeg/ffmpeg-git-20220910-amd64-static/"; //linux中,路径为 /root/moch/ffmpeg-4.4-amd64-static/ 你自己在linux服务器上安装的路径
  11. /**
  12. * 音频转换器
  13. * @param resourcePath 需要被转换的音频文件全路径带文件名
  14. * @param targetPath 转换之后的音频文件全路径带文件名
  15. */
  16. public static void audioConverter(String resourcePath, String targetPath) {
  17. formatConverter(new File(resourcePath), new File(targetPath), false);
  18. }
  19. /**
  20. * 视频转换器
  21. * @param resourcePath 需要被转换的视频文件全路径带文件名
  22. * @param targetPath 转换之后的视频文件全路径带文件名
  23. */
  24. public static void videoConverter(String resourcePath, String targetPath) {
  25. formatConverter(new File(resourcePath), new File(targetPath), true);
  26. }
  27. /**
  28. * 文件格式转换器
  29. * 注意!此方法为按照需求进行拼接命令来完成音频视频文件的处理 命令拼接需要根据自己需求进行更改
  30. * 视频 或 音频
  31. * @param fileInput 源文件路径
  32. * @param fileOutPut 转换后的文件路径
  33. * @param isVideo 源文件是视频文件
  34. *
  35. */
  36. public static void formatConverter(File fileInput, File fileOutPut, boolean isVideo) {
  37. fileInput.setExecutable(true);//设置可执行权限
  38. fileInput.setReadable(true);//设置可读权限
  39. fileInput.setWritable(true);//设置可写权限
  40. fileOutPut.setExecutable(true);//设置可执行权限
  41. fileOutPut.setReadable(true);//设置可读权限
  42. fileOutPut.setWritable(true);//设置可写权限
  43. if (null == fileInput || !fileInput.exists()) {
  44. throw new RuntimeException("源文件不存在,请检查源路径");
  45. }
  46. if (null == fileOutPut) {
  47. throw new RuntimeException("转换后的路径为空,请检查转换后的存放路径是否正确");
  48. }
  49. if (!fileOutPut.exists()) {
  50. try {
  51. fileOutPut.createNewFile();
  52. } catch (IOException e) {
  53. System.out.println("转换时新建输出文件失败");
  54. }
  55. }
  56. List<String> commond = new ArrayList<String>();
  57. //输出直接覆盖文件
  58. commond.add("-y");
  59. commond.add("-i");
  60. commond.add(fileInput.getAbsolutePath());
  61. if (isVideo) {
  62. commond.add("-vcodec");
  63. commond.add("libx264");
  64. commond.add("-mbd");
  65. commond.add("0");
  66. commond.add("-c:a");
  67. commond.add("aac");
  68. commond.add("-s");
  69. commond.add("720*720");
  70. commond.add("-threads"); //指定同时启动线程执行数, 经测试到10再大速度几无变化
  71. commond.add("25");
  72. commond.add("-preset");
  73. commond.add("ultrafast");
  74. commond.add("-strict");
  75. commond.add("-2");
  76. commond.add("-pix_fmt");
  77. commond.add("yuv420p");
  78. commond.add("-movflags");
  79. commond.add("faststart");
  80. }
  81. commond.add(fileOutPut.getAbsolutePath());
  82. //执行命令
  83. executeCommand(commond);
  84. }
  85. /**
  86. * 执行FFmpeg命令
  87. * @param commonds 要执行的FFmpeg命令
  88. * @return FFmpeg程序在执行命令过程中产生的各信息,执行出错时返回null
  89. */
  90. public static String executeCommand(List<String> commonds) {
  91. if (CollectionUtils.isEmpty(commonds)) {
  92. System.out.println("--- 指令执行失败,因为要执行的FFmpeg指令为空! ---");
  93. return null;
  94. }
  95. LinkedList<String> ffmpegCmds = new LinkedList<>(commonds);
  96. ffmpegCmds.addFirst(FFMPEG_PATH); // 设置ffmpeg程序所在路径
  97. System.out.println("--- 待执行的FFmpeg指令为:---" + ffmpegCmds);
  98. Runtime runtime = Runtime.getRuntime();
  99. Process ffmpeg = null;
  100. try {
  101. // 执行ffmpeg指令
  102. ProcessBuilder builder = new ProcessBuilder("/bin/sh", "-c");
  103. builder.command(ffmpegCmds);
  104. ffmpeg = builder.start();
  105. System.out.println("--- 开始执行FFmpeg指令:--- 执行线程名:" + builder.toString());
  106. // 取出输出流和错误流的信息
  107. // 注意:必须要取出ffmpeg在执行命令过程中产生的输出信息,如果不取的话当输出流信息填满jvm存储输出留信息的缓冲区时,线程就回阻塞住
  108. PrintStream errorStream = new PrintStream(ffmpeg.getErrorStream());
  109. PrintStream inputStream = new PrintStream(ffmpeg.getInputStream());
  110. errorStream.start();
  111. inputStream.start();
  112. // 等待ffmpeg命令执行完
  113. ffmpeg.waitFor();
  114. // 获取执行结果字符串
  115. String result = errorStream.stringBuffer.append(inputStream.stringBuffer).toString();
  116. // 输出执行的命令信息
  117. String cmdStr = Arrays.toString(ffmpegCmds.toArray()).replace(",", "");
  118. String resultStr = StringUtils.isBlank(result) ? "【异常】" : "正常";
  119. System.out.println("--- 已执行的FFmepg命令: ---" + cmdStr + " 已执行完毕,执行结果: " + resultStr);
  120. return result;
  121. } catch (Exception e) {
  122. System.out.println("--- FFmpeg命令执行出错! --- 出错信息: " + e.getMessage());
  123. return null;
  124. } finally {
  125. if (null != ffmpeg) {
  126. ProcessKiller ffmpegKiller = new ProcessKiller(ffmpeg);
  127. // JVM退出时,先通过钩子关闭FFmepg进程
  128. runtime.addShutdownHook(ffmpegKiller);
  129. }
  130. }
  131. }
  132. /**
  133. * 用于取出ffmpeg线程执行过程中产生的各种输出和错误流的信息
  134. */
  135. static class PrintStream extends Thread {
  136. InputStream inputStream = null;
  137. BufferedReader bufferedReader = null;
  138. StringBuffer stringBuffer = new StringBuffer();
  139. public PrintStream(InputStream inputStream) {
  140. this.inputStream = inputStream;
  141. }
  142. @Override
  143. public void run() {
  144. try {
  145. if (null == inputStream) {
  146. System.out.println("--- 读取输出流出错!因为当前输出流为空!---");
  147. }
  148. bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
  149. String line = null;
  150. while ((line = bufferedReader.readLine()) != null) {
  151. System.out.println(line);
  152. stringBuffer.append(line);
  153. }
  154. } catch (Exception e) {
  155. System.out.println("--- 读取输入流出错了!--- 错误信息:" + e.getMessage());
  156. } finally {
  157. try {
  158. if (null != bufferedReader) {
  159. bufferedReader.close();
  160. }
  161. if (null != inputStream) {
  162. inputStream.close();
  163. }
  164. } catch (IOException e) {
  165. System.out.println("--- 调用PrintStream读取输出流后,关闭流时出错!---");
  166. }
  167. }
  168. }
  169. }
  170. /**
  171. * 在程序退出前结束已有的FFmpeg进程
  172. */
  173. private static class ProcessKiller extends Thread {
  174. private Process process;
  175. public ProcessKiller(Process process) {
  176. this.process = process;
  177. }
  178. @Override
  179. public void run() {
  180. this.process.destroy();
  181. System.out.println("--- 已销毁FFmpeg进程 --- 进程名: " + process.toString());
  182. }
  183. }
  184. /**
  185. * 获取音频基本信息
  186. *
  187. * @param path 文件路径|URL
  188. * @throws EncoderException
  189. */
  190. public static MultimediaInfo testMediaInfo(String path) throws EncoderException, MalformedURLException {
  191. MultimediaObject instance;
  192. if (path.startsWith("http")) {
  193. instance = new MultimediaObject(new URL(path));
  194. } else {
  195. instance = new MultimediaObject(new File(path));
  196. }
  197. return instance.getInfo();
  198. }
  199. /**
  200. * 原生调用ffmpeg获取音频基本信息
  201. *
  202. * @param urlPath
  203. */
  204. public static void testFFmpeg(String urlPath) {
  205. ProcessLocator processLocator = new DefaultFFMPEGLocator();
  206. ProcessWrapper ffmpeg = processLocator.createExecutor();
  207. ffmpeg.addArgument("-i");
  208. ffmpeg.addArgument(urlPath);
  209. try {
  210. ffmpeg.execute();
  211. String res = IOUtils.toString(ffmpeg.getErrorStream(), "UTF-8");
  212. } catch (Exception e) {
  213. e.printStackTrace();
  214. } finally {
  215. ffmpeg.destroy();
  216. }
  217. }
  218. /**
  219. * 转成Mp4
  220. *
  221. * @param sourceFile
  222. * @param distFile
  223. * @param pListener
  224. * @throws EncoderException
  225. */
  226. public static void codecToMp4(String sourceFile, String distFile, EncoderProgressListener pListener) throws EncoderException {
  227. File source = new File(sourceFile);
  228. File target = new File(distFile);
  229. if (target.exists()) {
  230. target.delete();
  231. }
  232. AudioAttributes audioAttr = new AudioAttributes();
  233. VideoAttributes videoAttr = new VideoAttributes();
  234. EncodingAttributes encodingAttr = new EncodingAttributes();
  235. audioAttr.setChannels(2);
  236. audioAttr.setCodec("aac");
  237. audioAttr.setBitRate(128000);
  238. audioAttr.setSamplingRate(44100);
  239. videoAttr.setCodec("libx264");
  240. videoAttr.setBitRate(2 * 1024 * 1024);
  241. videoAttr.setSize(new VideoSize(1080, 720));
  242. videoAttr.setFaststart(true);
  243. videoAttr.setFrameRate(29);
  244. encodingAttr.setAudioAttributes(audioAttr);
  245. encodingAttr.setVideoAttributes(videoAttr);
  246. encodingAttr.setOutputFormat("mp4");
  247. Encoder encoder = new Encoder();
  248. encoder.encode(new MultimediaObject(source), target, encodingAttr, pListener);
  249. }
  250. /**
  251. * 添加文字水印
  252. *
  253. * @param sourceFile
  254. * @param distFile
  255. * @param textWaterMark
  256. * @param pListener
  257. * @throws EncoderException
  258. */
  259. public static void codecToMp4WithText(String sourceFile, String distFile, String textWaterMark, EncoderProgressListener pListener) throws EncoderException {
  260. File sourceVideo = new File(sourceFile);
  261. File target = new File(distFile);
  262. if (target.exists()) {
  263. target.delete();
  264. }
  265. DrawtextFilter vf = new DrawtextFilter(textWaterMark, "(w-text_w)/2", "(h-text_h)/2", "宋体", 30.0, new Color("ffffff", "44"));
  266. vf.setShadow(new Color("000000", "44"), 2, 2);
  267. VideoAttributes videoAttributes = new VideoAttributes();
  268. videoAttributes.addFilter(vf);
  269. EncodingAttributes attrs = new EncodingAttributes();
  270. attrs.setVideoAttributes(videoAttributes);
  271. Encoder encoder = new Encoder();
  272. encoder.encode(new MultimediaObject(sourceVideo), target, attrs, pListener);
  273. }
  274. //
  275. public static void main(String[] args) throws EncoderException, MalformedURLException {
  276. String videoPath = "D:\\usr\\local\\tobaccocasedoc\\Download\\test(0).mp4";
  277. String wavPath = "D:\\usr\\local\\tobaccocasedoc\\Download\\test(0).mp4";
  278. String mp3Path = "D:\\usr\\local\\tobaccocasedoc\\Download\\result.mp4";
  279. //测试获取视频信息
  280. MultimediaInfo info = testMediaInfo(videoPath);
  281. System.out.println(JSON.toJSONString(info));
  282. //测试音频转码
  283. codecToMp4(wavPath, mp3Path, new EncoderProgressListener() {
  284. @Override
  285. public void sourceInfo(MultimediaInfo info) {
  286. System.out.println(JSON.toJSONString(info));
  287. }
  288. @Override
  289. public void progress(int permil) {
  290. System.out.println(permil);
  291. }
  292. @Override
  293. public void message(String message) {
  294. System.out.println(message);
  295. }
  296. });
  297. }

(注: 因为官方的sdk不定时更新,一些接口参数也可能发生改变 大家可以根据返回的错误码判断错误信息 这篇文章仅供大家参考,如需要和我版本一致的sdk,源码下载类替换就好啦)

源码地址:(晚点上传)

对应的jar和maven需要大佬们自己去找找了

大佬勿喷!!!!!!!!!!!!!!!!!!!!! 写这个博客只是希望可以帮到大家

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/盐析白兔/article/detail/102872
推荐阅读
相关标签
  

闽ICP备14008679号