package com.netsdk.demo.customize; import com.netsdk.demo.customize.analyseTaskDemo.AnalyseTaskUtils; import com.netsdk.demo.util.CaseMenu; import com.netsdk.lib.NetSDKLib; import com.netsdk.lib.ToolKits; import com.netsdk.lib.callback.impl.DefaultDisconnectCallback; import com.netsdk.lib.callback.impl.DefaultHaveReconnectCallBack; import com.netsdk.lib.enumeration.NET_EM_SHAPE_TYPE; import com.netsdk.lib.structure.*; import com.sun.jna.ptr.IntByReference; import java.io.File; import java.io.UnsupportedEncodingException; import java.util.Scanner; import static com.netsdk.lib.Utils.getOsPrefix; /** * @author : 47040 * @since : Created in 2020/10/26 17:38 */ public class ScenicSpotDemo { // The constant net sdk public static final NetSDKLib netsdk = NetSDKLib.NETSDK_INSTANCE; // The constant config sdk. public static final NetSDKLib configsdk = NetSDKLib.CONFIG_INSTANCE; public static String encode; static { String osPrefix = getOsPrefix(); if (osPrefix.toLowerCase().startsWith("win32-amd64")) { encode = "GBK"; } else if (osPrefix.toLowerCase().startsWith("linux-amd64")) { encode = "UTF-8"; } } ////////////////////////////////// SDK 初始化相关 ////////////////////////////// /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// private static boolean bInit = false; // 判断是否初始化 private static boolean bLogOpen = false; // 判断log是否打开 // 回调函数需要是静态的,防止被系统回收 private static final NetSDKLib.fDisConnect disConnectCB = DefaultDisconnectCallback.getINSTANCE(); // 断线回调 private static final NetSDKLib.fHaveReConnect haveReConnectCB = DefaultHaveReconnectCallBack.getINSTANCE(); // 重连回调 /** * Init boolean. sdk 初始化 * * @return the boolean */ public static boolean Init() { bInit = netsdk.CLIENT_Init(disConnectCB, null); if (!bInit) { System.err.println("Initialize SDK failed"); return false; } System.out.println("Initialize SDK Succeed"); EnableLog(); // 配置日志 // 设置断线重连回调接口, 此操作为可选操作,但建议用户进行设置 netsdk.CLIENT_SetAutoReconnect(haveReConnectCB, null); //设置登录超时时间和尝试次数,可选 int waitTime = 3000; //登录请求响应超时时间设置为3S int tryTimes = 1; //登录时尝试建立链接 1 次 netsdk.CLIENT_SetConnectTime(waitTime, tryTimes); // 设置更多网络参数, NET_PARAM 的nWaittime , nConnectTryNum 成员与 CLIENT_SetConnectTime // 接口设置的登录设备超时时间和尝试次数意义相同,可选 NetSDKLib.NET_PARAM netParam = new NetSDKLib.NET_PARAM(); netParam.nConnectTime = 10000; // 登录时尝试建立链接的超时时间 netParam.nGetConnInfoTime = 3000; // 设置子连接的超时时间, 拉流的响应时间也是这个 netsdk.CLIENT_SetNetworkParam(netParam); return true; } /** * 打开 sdk log */ private static void EnableLog() { NetSDKLib.LOG_SET_PRINT_INFO setLog = new NetSDKLib.LOG_SET_PRINT_INFO(); File path = new File("sdklog/"); if (!path.exists()) path.mkdir(); // 这里的log保存地址依据实际情况自己调整 String logPath = path.getAbsoluteFile().getParent() + "\\sdklog\\" + "sdklog" + AnalyseTaskUtils.getDate() + ".log"; setLog.nPrintStrategy = 0; setLog.bSetFilePath = 1; System.arraycopy(logPath.getBytes(), 0, setLog.szLogFilePath, 0, logPath.getBytes().length); setLog.bSetPrintStrategy = 1; bLogOpen = netsdk.CLIENT_LogOpen(setLog); if (!bLogOpen) System.err.println("Failed to open NetSDK log"); System.out.println("Open NetSDK Log Succeed"); System.out.println(logPath); } /** * Cleanup. 清除 sdk 环境 */ public static void Cleanup() { if (bLogOpen) { netsdk.CLIENT_LogClose(); System.out.println("NetSDK Log Closed"); } if (bInit) { netsdk.CLIENT_Cleanup(); System.out.println("NetSDK Clean Up Succeed"); } } /** * 清理并退出 */ public static void CleanAndExit() { netsdk.CLIENT_Cleanup(); System.exit(0); } ////////////////////////////////// SDK 登录相关 //////////////////////////////// /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// public NetSDKLib.NET_DEVICEINFO_Ex deviceInfo = new NetSDKLib.NET_DEVICEINFO_Ex(); // 设备信息 public NetSDKLib.LLong m_hLoginHandle = new NetSDKLib.LLong(0); // 登录句柄 // 高安全登陆 private void Login(String strIp, int port, String strUser, String strPassword) { // 入参 NetSDKLib.NET_IN_LOGIN_WITH_HIGHLEVEL_SECURITY pstlnParam = new NetSDKLib.NET_IN_LOGIN_WITH_HIGHLEVEL_SECURITY(); System.arraycopy(strIp.getBytes(), 0, pstlnParam.szIP, 0, strIp.length()); // IP pstlnParam.nPort = port; // Port System.arraycopy(strUser.getBytes(), 0, pstlnParam.szUserName, 0, strUser.length()); // Username System.arraycopy(strPassword.getBytes(), 0, pstlnParam.szPassword, 0, strPassword.length()); // Password // 出参 NetSDKLib.NET_OUT_LOGIN_WITH_HIGHLEVEL_SECURITY pstOutParam = new NetSDKLib.NET_OUT_LOGIN_WITH_HIGHLEVEL_SECURITY(); // 高安全登陆 m_hLoginHandle = netsdk.CLIENT_LoginWithHighLevelSecurity(pstlnParam, pstOutParam); if (m_hLoginHandle.longValue() != 0) { System.out.printf("Login Device[%s] Succeed!\n", strIp); deviceInfo = pstOutParam.stuDeviceInfo; // 获取设备信息 System.out.println("Device Address: " + strIp + " Port: " + port); System.out.println("设备包含:" + deviceInfo.byChanNum + "个通道"); } else { System.err.printf("Login Device[%s] Failed.Error[%s]\n", m_strIp, ToolKits.getErrorCode()); LoginOut(); } } // 登出 private void LoginOut() { if (m_hLoginHandle.longValue() != 0) { netsdk.CLIENT_Logout(m_hLoginHandle); m_hLoginHandle.setValue(0); } } /////////////////////////////////// 景物信息管理接口 //////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////// private final Scanner sc = new Scanner(System.in); // 控制台输入 /** * 获取景物点支持的能力 */ public void TestScenicSpotGetCaps() { System.out.println("请输入全景相机的通道号(SDK从0计数):"); int channel = sc.nextInt(); NET_IN_SCENICSPOT_GETCAPS_INFO stuInGetCapInfo = new NET_IN_SCENICSPOT_GETCAPS_INFO(); stuInGetCapInfo.nChannel = channel; NET_OUT_SCENICSPOT_GETCAPS_INFO stuOutGetCapInfo = new NET_OUT_SCENICSPOT_GETCAPS_INFO(); stuInGetCapInfo.write(); stuOutGetCapInfo.write(); boolean ret = netsdk.CLIENT_ScenicSpotGetCaps(m_hLoginHandle, stuInGetCapInfo.getPointer(), stuOutGetCapInfo.getPointer(), 3000); if (!ret) { System.err.println(String.format("获取景物点支持能力失败: %s", ToolKits.getErrorCode())); } System.out.println("获取景物点支持能力成功"); stuOutGetCapInfo.read(); // 打印信息 StringBuilder capInfo = new StringBuilder().append(String.format("————————————通道[%3d] 景物点支持的能力情况————————\n", channel)) .append("bEnable 是否支持景物点功能: ").append(stuOutGetCapInfo.stuCaps.bEnable == 0 ? "不支持" : "支持").append("\n"); if (stuOutGetCapInfo.stuCaps.bEnable != 0) { capInfo.append("nTotalNum 最多支持景物点数: ").append(stuOutGetCapInfo.stuCaps.nTotalNum).append("\n") .append("nRegionNum 单画面最多支持景物点数: ").append(stuOutGetCapInfo.stuCaps.nRegionNum); } System.out.println(capInfo.toString()); } /** * 获取所有景物点信息 * 8 目全景 AR 支持 256 路,查询全部会看见多出的最后一路,而且编号也太对,先不要使用它,用前256路 * 另外注意,通道必须指定是全景相机的通道,设置在球机通道内是无效的 */ public void TestScenicSpotGetPointInfos() { System.out.println("请输入全景相机的通道号(SDK从0计数):"); int channel = sc.nextInt(); // 景物点的信息不是一次性获取的,而是根据 nOffset 和 nLimit 参数多次获取,类似于网页分页查询 // 请根据自己的网络状况合理设置参数 // 这里 我以10条一次的方式 顺序获取所有景物点信息 int nMaxFetch = 10; int offset = 0; NET_IN_SCENICSPOT_GETPOINTINFOS_INFO stuInGetPointInfo = new NET_IN_SCENICSPOT_GETPOINTINFOS_INFO(); stuInGetPointInfo.nChannelID = channel; stuInGetPointInfo.nOffset = offset; stuInGetPointInfo.nLimit = nMaxFetch; NET_OUT_SCENICSPOT_GETPOINTINFOS_INFO stuOutGetPointInfo = new NET_OUT_SCENICSPOT_GETPOINTINFOS_INFO(); ScenicSpoteGetPointInfos(stuInGetPointInfo, stuOutGetPointInfo, m_hLoginHandle); // 景物点信息总数 int totalCount = stuOutGetPointInfo.nTotal; // 打印信息 int retCount = stuOutGetPointInfo.nRetSceneNum; PrintScenicSpotInfos(stuOutGetPointInfo, retCount); offset += retCount; // 说明还有没获取的信息, 循环继续获取 while (!(retCount < nMaxFetch)) { stuInGetPointInfo.nOffset = offset; ScenicSpoteGetPointInfos(stuInGetPointInfo, stuOutGetPointInfo, m_hLoginHandle); retCount = stuOutGetPointInfo.nRetSceneNum; PrintScenicSpotInfos(stuOutGetPointInfo, retCount); offset += retCount; } } /** * 获取单个景物点信息 * 提示: 景物点的编号 nIndex 默认是和偏移量 nOffset 一一对应的,设置时也请不要破坏这种对应关系 * 另外注意,通道必须指定是全景相机的通道,设置在球机通道内是无效的 */ public void TestScenicSpotGetPointInfoSingle() { System.out.println("请输入全景相机的通道号(SDK从0计数):"); int channel = sc.nextInt(); System.out.println("请输入需要获取景物点信息的序号(从0计数):"); int offset = sc.nextInt(); // 景物点的信息是根据 nOffset 和 nLimit 参数获取的,类似于网页分页查询 // 指定 nOffset 并设置 nLimit = 1 即可获取指定景物点信息 NET_IN_SCENICSPOT_GETPOINTINFOS_INFO stuInGetPointInfo = new NET_IN_SCENICSPOT_GETPOINTINFOS_INFO(); stuInGetPointInfo.nChannelID = channel; stuInGetPointInfo.nOffset = offset; stuInGetPointInfo.nLimit = 1; NET_OUT_SCENICSPOT_GETPOINTINFOS_INFO stuOutGetPointInfo = new NET_OUT_SCENICSPOT_GETPOINTINFOS_INFO(); ScenicSpoteGetPointInfos(stuInGetPointInfo, stuOutGetPointInfo, m_hLoginHandle); // 景物点信息总数 int totalCount = stuOutGetPointInfo.nTotal; // 打印信息 int retCount = stuOutGetPointInfo.nRetSceneNum; PrintScenicSpotInfos(stuOutGetPointInfo, retCount); } // 打印景物点信息组 private static void PrintScenicSpotInfos(NET_OUT_SCENICSPOT_GETPOINTINFOS_INFO stuOutGetPointInfo, int retCount) { for (int i = 0; i < retCount; i++) { StringBuilder info = new StringBuilder(); try { info.append("nIndex 编号: ").append(stuOutGetPointInfo.stuPointInfos[i].nIndex).append("\n") .append("bEnable 是否生效: ").append(stuOutGetPointInfo.stuPointInfos[i].bEnable == 0 ? "不生效" : "生效").append("\n") .append("bTitleAttribute 是否有子标题: ").append(stuOutGetPointInfo.stuPointInfos[i].bTitleAttribute == 0 ? "是" : "否").append("\n") .append("stuPoint 景物点8192坐标: ").append( String.format("(%4d, %4d)", stuOutGetPointInfo.stuPointInfos[i].stuPoint.nX, stuOutGetPointInfo.stuPointInfos[i].stuPoint.nY)).append("\n") .append("szTitleName 一级标题名称: ").append(new String(stuOutGetPointInfo.stuPointInfos[i].szTitleName, encode)).append("\n") .append("byTitleType 一级标题类型: ").append(stuOutGetPointInfo.stuPointInfos[i].byTitleType).append("\n") .append("emShapeType 景物形状: ").append(NET_EM_SHAPE_TYPE.getNoteByValue(stuOutGetPointInfo.stuPointInfos[i].emShapeType)).append("\n") .append("nRetPolygonPointNum 轮廓点个数: ").append(stuOutGetPointInfo.stuPointInfos[i].nRetPolygonPointNum).append("\n"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } for (int j = 0; j < stuOutGetPointInfo.stuPointInfos[i].nRetPolygonPointNum; j++) { info.append(String.format("轮廓点[%2d]:(%4d, %4d)\n", j, stuOutGetPointInfo.stuPointInfos[i].stuPolygon[j].nx, stuOutGetPointInfo.stuPointInfos[i].stuPolygon[j].ny)); } System.out.println(info.append("——————————————————————————————\n").toString()); } } // 获取景物点信息 private static void ScenicSpoteGetPointInfos(NET_IN_SCENICSPOT_GETPOINTINFOS_INFO stuInGetPointInfo, NET_OUT_SCENICSPOT_GETPOINTINFOS_INFO stuOutGetPointInfo, NetSDKLib.LLong m_hLoginHandle) { stuInGetPointInfo.write(); stuOutGetPointInfo.write(); boolean ret = netsdk.CLIENT_ScenicSpotGetPointInfos(m_hLoginHandle, stuInGetPointInfo.getPointer(), stuOutGetPointInfo.getPointer(), 3000); if (!ret) { System.err.println(String.format("获取景物点信息失败: %s", ToolKits.getErrorCode())); } stuOutGetPointInfo.read(); } /** * 设置景物点信息 * 特别指出:Polygon 即轮廓坐标也是全景相机的 8192 坐标,不是球机的 * 另外注意,通道必须指定是全景相机的通道,设置在球机通道内是无效的 */ public void TestScenicSpotSetPointInfo() { System.out.println("请输入全景相机的通道号(SDK从0计数):"); int channel = sc.nextInt(); System.out.println("请输入需要获取景物点信息的序号(从0计数):"); int offset = sc.nextInt(); /** * 这里的用例是: * { * "channel": #{channel}, * "Index": #{offset}, * "Enable": true, * "TitleAttribute": true, * "Point": [ 6642, 5127 ], * "ShapType": 0, * "Polygon": [[6000,5000],[6000,6000],[7000,6000],[7000,5000]], * "TitleName": "景物 001", * "TitleType": 1 * } */ ///////////////////////////////// 获取原先信息 ////////////////////////////////// ////////////////////// 理论上,在原来的基础上修改是最合适的 //////////////////////// /////////////////////////////////////////////////////////////////////////////// NET_IN_SCENICSPOT_GETPOINTINFOS_INFO stuInGetPointInfo = new NET_IN_SCENICSPOT_GETPOINTINFOS_INFO(); stuInGetPointInfo.nChannelID = channel; stuInGetPointInfo.nOffset = offset; stuInGetPointInfo.nLimit = 1; NET_OUT_SCENICSPOT_GETPOINTINFOS_INFO stuOutGetPointInfo = new NET_OUT_SCENICSPOT_GETPOINTINFOS_INFO(); ScenicSpoteGetPointInfos(stuInGetPointInfo, stuOutGetPointInfo, m_hLoginHandle); POINTINFOS prePointInfo = stuOutGetPointInfo.stuPointInfos[0]; ///////////////////////////////// 设置新的信息 ////////////////////////////////// ///////////////////// 当然这里,出于示例,我把所有有效字段都填一下 ////////////////// /////////////////////////////////////////////////////////////////////////////// NET_IN_SCENICSPOT_SETPOINTINFO_INFO stuInSetPointInfo = new NET_IN_SCENICSPOT_SETPOINTINFO_INFO(); stuInSetPointInfo.nChannel = channel; stuInSetPointInfo.nIndex = offset; stuInSetPointInfo.bEnable = 1; // "Enable": true 启用AR标签 byte[] szTitleName = new byte[0]; try { szTitleName = "景物001".getBytes(encode); // 标签名称,中文字符必须指定编码 } catch (UnsupportedEncodingException e) { e.printStackTrace(); } System.arraycopy(szTitleName, 0, stuInSetPointInfo.szTitleName, 0, szTitleName.length); // "TitleName": "景物001" stuInSetPointInfo.byTitleType = 1; // "TitleType": 1 标题类型 协议没有列出具体有哪些类型 默认写1 stuInSetPointInfo.emShapeType = NET_EM_SHAPE_TYPE.NET_EM_SHAPE_TYPE_MANSARD.getValue(); // "ShapType": 0 轮廓类型 stuInSetPointInfo.stuPoint = new POINTCOORDINATE(6642, 5127); // "Point": [ 6642, 5127 ] 云台在全景相机内的坐标 stuInSetPointInfo.nPolygonPointNum = 4; // "Polygon" 轮廓坐标集,全景坐标的8192坐标 stuInSetPointInfo.stuPolygon[0] = new NetSDKLib.DH_POINT((short) 6000, (short) 5000); stuInSetPointInfo.stuPolygon[1] = new NetSDKLib.DH_POINT((short) 6000, (short) 6000); stuInSetPointInfo.stuPolygon[2] = new NetSDKLib.DH_POINT((short) 7000, (short) 6000); stuInSetPointInfo.stuPolygon[3] = new NetSDKLib.DH_POINT((short) 7000, (short) 5000); stuInSetPointInfo.bTitleAttribute = 1; // "TitleAttribute": true 有子标题 // 由于OSD是在8900平台端叠加的,此Demo无法验证具体叠加效果。 "TitleType" "TitleAttribute" 的具体参数可能需要依据在平台端的测试结果作调整 NET_OUT_SCENICSPOT_SETPOINTINFO_INFO stuOutSetPointInfo = new NET_OUT_SCENICSPOT_SETPOINTINFO_INFO(); stuInSetPointInfo.write(); stuOutSetPointInfo.write(); boolean ret = netsdk.CLIENT_ScenicSpotSetPointInfo(m_hLoginHandle, stuInSetPointInfo.getPointer(), stuOutSetPointInfo.getPointer(), 3000); if (!ret) { System.err.println(String.format("设置景物点信息失败: %s", ToolKits.getErrorCode())); } System.out.println("设置景物点信息成功"); } /** * 以景物标注点为中心,进行三维定位 * 特别指出: 由于AR标签并不保存球机三维坐标( NET_IN_SCENICSPOT_SETPOINTINFO_INFO内的 stuPosition是无效字段) * 所以球机只会依据全景坐标点平移,不会 放大/缩小 * 另外,景物轮廓的OSD显示是8900平台端的功能,设备的预览界面并不会显示。 * 注意,通道必须指定是全景相机的通道,设置在球机通道内是无效的 */ public void TestScenicSpotTurnToPoint() { System.out.println("请输入全景相机的通道号(SDK从0计数):"); int channel = sc.nextInt(); System.out.println("请输入需要进行三维定位景物点的编号(从0计数):"); int index = sc.nextInt(); NET_IN_SCENICSPOT_TURNTOPOINT_INFO stuInTurnToPoint = new NET_IN_SCENICSPOT_TURNTOPOINT_INFO(); stuInTurnToPoint.nChannel = channel; stuInTurnToPoint.nIndex = index; NET_OUT_SCENICSPOT_TURNTOPOINT_INFO stuOutTurnToPoint = new NET_OUT_SCENICSPOT_TURNTOPOINT_INFO(); stuInTurnToPoint.write(); stuOutTurnToPoint.write(); boolean ret = netsdk.CLIENT_ScenicSpotTurnToPoint(m_hLoginHandle, stuInTurnToPoint.getPointer(), stuOutTurnToPoint.getPointer(), 3000); if (!ret) { System.err.println(String.format("景物点三维定位失败: %s", ToolKits.getErrorCode())); } System.out.println("景物点三维定位成功"); } /////////////////////////////////////////////////////////////////////////////////////// /// 为了弥补缺少球机三维坐标的问题,我把 球机绝对坐标跳转 和 获取球机绝对坐标 的接口也列在这里 /// /////////////////////////////////////////////////////////////////////////////////////// /** * 球机绝对坐标跳转 * 注意,通道必须指定是球机通道 */ public boolean ptzControlPreciseControl() { System.out.println("请输入云台相机的通道号(SDK从0计数):"); int nChannelID = sc.nextInt(); System.out.println("请输入X坐标:"); int xParam = sc.nextInt(); System.out.println("请输入Y坐标:"); int yParam = sc.nextInt(); System.out.println("请输入Z坐标:"); int zoomParam = sc.nextInt(); /* * 1)xParam:水平角度(0~3600) * 2)yParm:垂直坐标(0~900) 即正 90度转角, 但现在很多设备已支持负转角,请以实际测试为准 * 3)zoomParm:变倍(1~128),变倍为档位,并非实际变倍倍数比如球机最大变倍能力为50倍,接口的变倍值填写20,实际变倍值为  50*(20/128) */ return netsdk.CLIENT_DHPTZControlEx(m_hLoginHandle, nChannelID, NetSDKLib.NET_EXTPTZ_ControlType.NET_EXTPTZ_EXACTGOTO, // 精确控制 xParam, yParam, zoomParam, 1); } /** * 获取球机绝对坐标 * 注意,通道必须指定是球机通道 */ public void ptzQueryPreciseControlStatus() { System.out.println("请输入云台相机的通道号(SDK从0计数):"); int nChannelID = sc.nextInt(); int nType = NetSDKLib.NET_DEVSTATE_PTZ_LOCATION; NetSDKLib.NET_PTZ_LOCATION_INFO ptzLocationInfo = new NetSDKLib.NET_PTZ_LOCATION_INFO(); ptzLocationInfo.nChannelID = nChannelID; IntByReference intRetLen = new IntByReference(); ptzLocationInfo.write(); boolean bRet = netsdk.CLIENT_QueryDevState(m_hLoginHandle, nType, ptzLocationInfo.getPointer(), ptzLocationInfo.size(), intRetLen, 3000); ptzLocationInfo.read(); if (bRet) { System.out.println("xParam:" + ptzLocationInfo.nPTZPan); System.out.println("yParam:" + ptzLocationInfo.nPTZTilt); System.out.println("zoomParam:" + ptzLocationInfo.nPTZZoom); } else { System.err.println("QueryDev Failed!" + ToolKits.getErrorCode()); } } /////////////////////////////////////// 简易控制台 /////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////// // 初始化测试 public void InitTest() { ScenicSpotDemo.Init(); // 初始化SDK库 this.Login(m_strIp, m_nPort, m_strUser, m_strPassword); // 高安全登录 } // 加载测试内容 public void RunTest() { CaseMenu menu = new CaseMenu(); menu.addItem(new CaseMenu.Item(this, "获取景物点支持的能力", "TestScenicSpotGetCaps")); menu.addItem(new CaseMenu.Item(this, "获取所有景物点信息", "TestScenicSpotGetPointInfos")); menu.addItem(new CaseMenu.Item(this, "获取单个景物点信息", "TestScenicSpotGetPointInfoSingle")); menu.addItem(new CaseMenu.Item(this, "设置景物点信息", "TestScenicSpotSetPointInfo")); menu.addItem(new CaseMenu.Item(this, "以景物标注点为中心进行三维定位", "TestScenicSpotTurnToPoint")); menu.addItem(new CaseMenu.Item(this, "获取当前云台坐标", "ptzQueryPreciseControlStatus")); menu.addItem(new CaseMenu.Item(this, "云台坐标跳转", "ptzControlPreciseControl")); menu.run(); } // 结束测试 public void EndTest() { System.out.println("End Test"); this.LoginOut(); // 退出 System.out.println("See You..."); ScenicSpotDemo.CleanAndExit(); // 清理资源并退出 } //////////////////////////////////////////////////////////////// public String m_strIp = "10.18.128.106"; public int m_nPort = 37777; public String m_strUser = "admin"; public String m_strPassword = "admin123"; //////////////////////////////////////////////////////////////// public static void main(String[] args) { ScenicSpotDemo demo = new ScenicSpotDemo(); if (args.length == 4) { demo.m_strIp = args[0]; demo.m_nPort = Integer.parseInt(args[1]); demo.m_strUser = args[2]; demo.m_strPassword = args[3]; } demo.InitTest(); demo.RunTest(); demo.EndTest(); } }