You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

204 lines
8.8 KiB

package com.inspect.nvr.service.camera;
import com.inspect.nvr.service.camera.login.DahuaLoginService;
import com.inspect.nvr.daHuaCarme.jna.NetSDKLib;
import com.inspect.nvr.daHuaCarme.jna.dahua.ToolKits;
import com.inspect.nvr.daHuaCarme.utils.jna.DahuaUtils;
import com.inspect.nvr.domain.Infrared.Camera;
import com.inspect.nvr.domain.Infrared.TemperatureData;
import com.inspect.nvr.enums.CameraEnum;
import com.sun.jna.Pointer;
import com.sun.jna.ptr.IntByReference;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 大华设备SDK服务
* 同一个IP,同一个通道,串行
* 同一个IP,不同通道,最大4个线程并发
* 不同IP,并发抓图,不限制
*/
@Slf4j
@Service
public class DahuaCameraService extends AbstractCameraService {
/**
* Digest认证抓图URL:
* http://<host>/cgi-bin/snapshot.cgi?channel=<channel>&subtype=<subtype>
* subtype: 0-主码流 1-子码流
*/
private static final String DIGEST_URL_TEMPLATE = "http://%s/cgi-bin/snapshot.cgi?channel=%d&subtype=1";
// Key: loginId+CmdSerial(登录句柄+流水号),Value: CompletableFuture(用于通知调用线程)
private static final ConcurrentHashMap<String, CompletableFuture<byte[]>> PENDING_REQUESTS = new ConcurrentHashMap<>();
private static final fCaptureReceiveCB CAPTURE_RECEIVE_CB = new fCaptureReceiveCB();
// 全局流水号生成器
private static final AtomicInteger SERIAL_COUNTER = new AtomicInteger(1);
@Resource
private NetSDKLib dhNetSDK;
@Resource
private DahuaLoginService dahuaLoginService;
// CmdSerial请求序列号,有效值范围 0~65535,超出范围会被截断
public static int nextSerial() {
return SERIAL_COUNTER.updateAndGet(current -> (current + 1) & 0xFFFF);
}
@Override
public CameraEnum cameraType() {
return CameraEnum.DAHUA;
}
@Override
protected String digestUrlTemplate() {
return DIGEST_URL_TEMPLATE;
}
@Override
protected byte[] doCapture(Camera camera) {
return snapPictureEx(camera);
}
/**
* 预置位跳转
*/
@Override
protected boolean doGotoPreset(Camera camera) {
NetSDKLib.LLong loginHandle = dahuaLoginService.login(camera);
log.info("[" + brandName() + "]预置位跳转,IP={},LoginHandle={},pointNum={}", camera.getIp(), loginHandle, camera.getPointNum());
boolean result = dhNetSDK.CLIENT_DHPTZControlEx2(loginHandle, camera.getChannel() - 1,
DahuaUtils.PTZCommand("GOTO_PRESET"), 0, camera.getPointNum(), 0, 0, null);
if (result) {
log.info("[" + brandName() + "]CLIENT_DHPTZControlEx success,pointNum={}", camera.getPointNum());
} else {
log.error("[" + brandName() + "]CLIENT_DHPTZControlEx Failed!!{}", ToolKits.getErrorCodePrint(dhNetSDK.CLIENT_GetLastError()));
}
return result;
}
/**
* 大华实时测温(蓝本:DahuaServiceImpl#StartRemote)。
* 通过 CLIENT_QueryDevInfo 查询测温规则区域温度。
*/
@Override
protected TemperatureData doMeasureTemperature(Camera camera) {
log.info("[" + brandName() + "]实时测温开始,ip={}, channel={}, presetId={}", camera.getIp(), camera.getChannel(), camera.getPresetId());
NetSDKLib.LLong loginHandle = dahuaLoginService.login(camera);
if (loginHandle == null) {
log.error("[" + brandName() + "]测温失败:登录句柄为空,ip={}", camera.getIp());
return null;
}
// 初始化输入结构体,设置条件参数
NetSDKLib.NET_IN_RADIOMETRY_GETTEMPER netIn = new NetSDKLib.NET_IN_RADIOMETRY_GETTEMPER();
netIn.stCondition.nPresetId = camera.getPresetId();
netIn.stCondition.nRuleId = camera.getRuleId();
netIn.stCondition.nMeterType = NetSDKLib.NET_RADIOMETRY_METERTYPE.NET_RADIOMETRY_METERTYPE_AREA;
netIn.stCondition.nChannel = camera.getChannel() - 1;
// 输出结构体
NetSDKLib.NET_OUT_RADIOMETRY_GETTEMPER netOut = new NetSDKLib.NET_OUT_RADIOMETRY_GETTEMPER();
netOut.stTempInfo = new NetSDKLib.NET_RADIOMETRYINFO();
netIn.write();
netOut.write();
boolean success = dhNetSDK.CLIENT_QueryDevInfo(
loginHandle,
NetSDKLib.NET_QUERY_DEV_RADIOMETRY_TEMPER,
netIn.getPointer(),
netOut.getPointer(),
null,
5000
);
if (!success) {
log.error("[" + brandName() + "]获取设备参数失败,错误码:{}", ToolKits.getErrorCodePrint(dhNetSDK.CLIENT_GetLastError()));
return null;
}
// 将本地内存同步到 Java 字段
netOut.read();
NetSDKLib.NET_RADIOMETRYINFO stTempInfo = netOut.stTempInfo;
log.info("[" + brandName() + "]测温结果:最高={}, 最低={}, 平均={}, 温差={}",
stTempInfo.fTemperMax, stTempInfo.fTemperMin, stTempInfo.fTemperAver, stTempInfo.fTemperStd);
return new TemperatureData(
String.valueOf(stTempInfo.fTemperMax),
String.valueOf(stTempInfo.fTemperMin),
stTempInfo.fTemperAver,
stTempInfo.fTemperStd,
netIn.stCondition.nPresetId,
netIn.stCondition.nRuleId
);
}
/**
* 大华SDK抓图具体实现(异步)
* 通过CompletableFuture实现异步回调通知
*/
private byte[] snapPictureEx(Camera camera) {
NetSDKLib.LLong loginID = dahuaLoginService.login(camera);
NetSDKLib.SNAP_PARAMS snapParams = new NetSDKLib.SNAP_PARAMS();
snapParams.Channel = camera.getChannel() - 1; // 通道号从0开始
snapParams.mode = 0; // 抓图模式:0-单次抓
snapParams.Quality = 3;
snapParams.InterSnap = 0;
int mySerialId = nextSerial();
snapParams.CmdSerial = mySerialId;
IntByReference reference = new IntByReference(0);
// 设置异步抓图回调函数
dhNetSDK.CLIENT_SetSnapRevCallBack(CAPTURE_RECEIVE_CB, null);
String requestKey = loginID.longValue() + ":" + mySerialId;
CompletableFuture<byte[]> future = new CompletableFuture<>();
PENDING_REQUESTS.put(requestKey, future);
final int TIMEOUT_SEC = 5;
try {
log.info("[" + brandName() + "]开始抓图,LoginID={},IP={},Channel={},Serial={}", loginID, camera.getIp(), camera.getChannel(), mySerialId);
boolean isCaptured = dhNetSDK.CLIENT_SnapPictureEx(loginID, snapParams, reference);
if (!isCaptured) {
String errorMsg = ToolKits.getErrorCodePrint(dhNetSDK.CLIENT_GetLastError());
throw new RuntimeException("SDK抓图失败:" + errorMsg);
}
return future.get(TIMEOUT_SEC, TimeUnit.SECONDS);
} catch (TimeoutException e) {
log.error("[" + brandName() + "]抓图超时:在 {} 秒内未收到设备回调,requestKey={}", TIMEOUT_SEC, requestKey);
} catch (Exception e) {
log.error("[" + brandName() + "]抓图异常:", e);
} finally {
PENDING_REQUESTS.remove(requestKey);
}
return null;
}
/**
* CLIENT_SnapPictureEx异步抓图回调函数重写
*/
public static class fCaptureReceiveCB implements NetSDKLib.fSnapRev {
@Override
public void invoke(NetSDKLib.LLong lLoginID, Pointer pBuf, int RevLen, int EncodeType, int CmdSerial, Pointer dwUser) {
// 检查是否有等待该流水号的请求
String requestKey = lLoginID.longValue() + ":" + CmdSerial;
CompletableFuture<byte[]> future = PENDING_REQUESTS.remove(requestKey);
if (future != null) {
log.info("[" + CameraEnum.DAHUA.getName() + "]匹配到抓图回调,LoginID={}, Serial={}", lLoginID, CmdSerial);
if (pBuf != null && RevLen > 0) {
// 读取图片数据
byte[] data = pBuf.getByteArray(0, RevLen);
// 完成Future,通知主线程
future.complete(data);
} else {
future.completeExceptionally(new RuntimeException("Empty image data"));
}
} else {
// 可能是由于超时已经被移除了,或者是其他类型的抓图
log.error("[" + CameraEnum.DAHUA.getName() + "]收到未匹配的抓图回调,LoginID={}, Serial={}", lLoginID, CmdSerial);
}
}
}
}