Browse Source

根据channel表可见光通道默认1,红外热成像默认2

master
WangGuangYuan 5 days ago
parent
commit
6ae9d66430
6 changed files with 162 additions and 18 deletions
  1. +3
    -1
      docker-compose.yml
  2. +93
    -13
      src/main/java/com/inspect/nvr/hik/service/HikNvrLiveStreamService.java
  3. +15
    -2
      src/main/java/com/inspect/nvr/hik/vo/HikNvrLiveStreamConfigVo.java
  4. +25
    -0
      src/main/java/com/inspect/nvr/ivs/mapper/BasedataEqpbookChannelMapper.java
  5. +5
    -2
      src/main/resources/application.yml
  6. +21
    -0
      src/main/resources/mapper/ivs/BasedataEqpbookChannelMapper.xml

+ 3
- 1
docker-compose.yml View File

@ -36,7 +36,9 @@ services:
# 实时预览优先直连摄像机取流(CVR 等设备远程转发额度小,直连可绕开该瓶颈)。 # 实时预览优先直连摄像机取流(CVR 等设备远程转发额度小,直连可绕开该瓶颈)。
HIK_NVR_LIVE_PREFER_CAMERA_DIRECT: ${HIK_NVR_LIVE_PREFER_CAMERA_DIRECT:-true} HIK_NVR_LIVE_PREFER_CAMERA_DIRECT: ${HIK_NVR_LIVE_PREFER_CAMERA_DIRECT:-true}
HIK_NVR_LIVE_CAMERA_RTSP_PORT: ${HIK_NVR_LIVE_CAMERA_RTSP_PORT:-554} HIK_NVR_LIVE_CAMERA_RTSP_PORT: ${HIK_NVR_LIVE_CAMERA_RTSP_PORT:-554}
# 同一 IP 多通道相机(双光谱)的相机内通道顺序:asc / desc。
# 台账 channel_type=ir(热成像)对应的相机内通道号:默认 2(1=可见光、2=热成像)。
HIK_NVR_LIVE_IR_CAMERA_CHANNEL: ${HIK_NVR_LIVE_IR_CAMERA_CHANNEL:-2}
# 同一 IP 多通道相机(双光谱)的相机内通道顺序:asc / desc,仅用于兜底。
HIK_NVR_LIVE_CAMERA_CHANNEL_ORDER: ${HIK_NVR_LIVE_CAMERA_CHANNEL_ORDER:-asc} HIK_NVR_LIVE_CAMERA_CHANNEL_ORDER: ${HIK_NVR_LIVE_CAMERA_CHANNEL_ORDER:-asc}
JAVA_TOOL_OPTIONS: ${JAVA_TOOL_OPTIONS:--XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0 -Dfile.encoding=UTF-8 -Djava.security.egd=file:/dev/./urandom -Djava.library.path=/app/lib:/app/lib/HCNetSDKCom:/app/lib/linux64} JAVA_TOOL_OPTIONS: ${JAVA_TOOL_OPTIONS:--XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0 -Dfile.encoding=UTF-8 -Djava.security.egd=file:/dev/./urandom -Djava.library.path=/app/lib:/app/lib/HCNetSDKCom:/app/lib/linux64}
REDIS_HOST: ${REDIS_HOST:?REDIS_HOST_required_in_.env} REDIS_HOST: ${REDIS_HOST:?REDIS_HOST_required_in_.env}


+ 93
- 13
src/main/java/com/inspect/nvr/hik/service/HikNvrLiveStreamService.java View File

@ -11,6 +11,7 @@ import com.inspect.nvr.hik.domain.HikNvrLiveStreamResponse;
import com.inspect.nvr.hik.domain.HikNvrPlaybackRequest; import com.inspect.nvr.hik.domain.HikNvrPlaybackRequest;
import com.inspect.nvr.hik.exception.HikNvrPlaybackException; import com.inspect.nvr.hik.exception.HikNvrPlaybackException;
import com.inspect.nvr.hik.vo.HikNvrLiveStreamConfigVo; import com.inspect.nvr.hik.vo.HikNvrLiveStreamConfigVo;
import com.inspect.nvr.ivs.mapper.BasedataEqpbookChannelMapper;
import com.inspect.nvr.ivs.mapper.IvsCameraMapper; import com.inspect.nvr.ivs.mapper.IvsCameraMapper;
import com.inspect.nvr.ivs.pojo.IvsCamera; import com.inspect.nvr.ivs.pojo.IvsCamera;
import com.inspect.nvr.ivs.service.IvsOpenApiService; import com.inspect.nvr.ivs.service.IvsOpenApiService;
@ -80,6 +81,10 @@ public class HikNvrLiveStreamService {
private static final String SOURCE_CAMERA = "camera"; private static final String SOURCE_CAMERA = "camera";
/** 取流来源:经 IVS/NVR(含 CVR 等中心存储设备)转发。 */ /** 取流来源:经 IVS/NVR(含 CVR 等中心存储设备)转发。 */
private static final String SOURCE_NVR = "nvr"; private static final String SOURCE_NVR = "nvr";
/** 台账 channel_type:红外(热成像)。 */
private static final String CHANNEL_TYPE_INFRARED = "ir";
/** 台账 channel_type:可见光。 */
private static final String CHANNEL_TYPE_VISIBLE = "vl";
/** 从 SDK 异常链中提取海康原始错误码。 */ /** 从 SDK 异常链中提取海康原始错误码。 */
private static final Pattern SDK_ERROR_PATTERN = private static final Pattern SDK_ERROR_PATTERN =
Pattern.compile("错误码[::]\\s*(\\d+)"); Pattern.compile("错误码[::]\\s*(\\d+)");
@ -102,6 +107,9 @@ public class HikNvrLiveStreamService {
/** 复用既有海康 RTSP 地址构造器,保证直连摄像机的鉴权编码规则一致。 */ /** 复用既有海康 RTSP 地址构造器,保证直连摄像机的鉴权编码规则一致。 */
@Resource @Resource
private HikPlaybackUrlBuilder urlBuilder; private HikPlaybackUrlBuilder urlBuilder;
/** 读取 basedata_eqpbook_channel.channel_type,按台账判定双光谱相机镜头类型。 */
@Resource
private BasedataEqpbookChannelMapper eqpbookChannelMapper;
/** FFmpeg 可执行文件路径。 */ /** FFmpeg 可执行文件路径。 */
private String ffmpegPath; private String ffmpegPath;
@ -145,6 +153,8 @@ public class HikNvrLiveStreamService {
private String cameraPassword; private String cameraPassword;
/** 直连摄像机 RTSP 端口连通性探测超时。 */ /** 直连摄像机 RTSP 端口连通性探测超时。 */
private int cameraProbeTimeoutMillis; private int cameraProbeTimeoutMillis;
/** 台账 channel_type=ir(热成像)对应的相机内通道号。 */
private int irCameraChannel;
/** 同一 IP 多通道相机的相机内通道分配顺序:asc / desc。 */ /** 同一 IP 多通道相机的相机内通道分配顺序:asc / desc。 */
private String cameraChannelOrder; private String cameraChannelOrder;
/** 已确认直连鉴权失败(相机凭据与 NVR 不同)的摄像机 IP,避免反复浪费一次尝试。 */ /** 已确认直连鉴权失败(相机凭据与 NVR 不同)的摄像机 IP,避免反复浪费一次尝试。 */
@ -200,6 +210,8 @@ public class HikNvrLiveStreamService {
? "" : config.getCameraPassword(); ? "" : config.getCameraPassword();
// 探测必须显著短于启动时限避免相机不可达时拖慢起流 // 探测必须显著短于启动时限避免相机不可达时拖慢起流
this.cameraProbeTimeoutMillis = Math.max(100, config.getCameraProbeTimeoutMillis()); this.cameraProbeTimeoutMillis = Math.max(100, config.getCameraProbeTimeoutMillis());
// 台账 channel_type=ir 的相机内通道号只接受 1/2其他取值按默认的 2 处理
this.irCameraChannel = config.getIrCameraChannel() == 1 ? 1 : 2;
// 多通道相机双光谱等的相机内通道分配顺序默认按平台通道编码升序 // 多通道相机双光谱等的相机内通道分配顺序默认按平台通道编码升序
this.cameraChannelOrder = "desc".equalsIgnoreCase( this.cameraChannelOrder = "desc".equalsIgnoreCase(
config.getCameraChannelOrder() == null ? "" : config.getCameraChannelOrder().trim()) config.getCameraChannelOrder() == null ? "" : config.getCameraChannelOrder().trim())
@ -1355,10 +1367,19 @@ public class HikNvrLiveStreamService {
* 计算平台通道对应的相机内通道号 * 计算平台通道对应的相机内通道号
* *
* <p>同一台多镜头相机双光谱可见光 + 热成像在平台上占多个通道直连 RTSP * <p>同一台多镜头相机双光谱可见光 + 热成像在平台上占多个通道直连 RTSP
* 必须带上相机自己的通道号否则两个平台通道会拉到同一个镜头优先按平台通道名判断
* 镜头类型热成像/测温 相机通道 1可见光 相机通道 2与海康双光谱默认一致
* 名称无法区分时按平台通道编码顺序分配 1..N整站顺序相反时用
* {@code hik.playback.live.camera-channel-order=desc} 反转</p>
* 必须带上相机自己的通道号否则两个平台通道会拉到同一个镜头判定顺序</p>
* <ol>
* <li>台账 {@code basedata_eqpbook_channel.channel_type}{@code vl} 可见光
* {@code ir} 红外热成像这是镜头类型的权威来源{@code ir} 用的相机内通道号
* {@code hik.playback.live.ir-camera-channel} 指定默认 2 1=可见光
* 2=热成像</li>
* <li>台账缺失或类型与镜头无关ptz/ng 时按平台通道名关键字判断</li>
* <li>名称也判断不出来时按平台通道编码顺序分配 1..N整站顺序相反时用
* {@code hik.playback.live.camera-channel-order=desc} 反转</li>
* </ol>
*
* <p>只有同 IP 多通道真双光谱机身机才做镜头判定单通道设备的相机内通道号
* 恒为 1单镜头热成像相机套用镜头映射反而会取到不存在的通道</p>
* *
* @param cameraCode 当前平台通道编码 * @param cameraCode 当前平台通道编码
* @param devIp 摄像机 IP * @param devIp 摄像机 IP
@ -1374,10 +1395,18 @@ public class HikNvrLiveStreamService {
devIp, queryFailure.getMessage()); devIp, queryFailure.getMessage());
return 1; return 1;
} }
// 单通道相机绝大多数直接使用相机通道 1
// 单通道相机绝大多数直接使用相机通道 1不必再查台账
if (peers == null || peers.size() <= 1) { if (peers == null || peers.size() <= 1) {
return 1; return 1;
} }
// IP 多通道说明是多镜头机身相机先按台账镜头类型判定避免依赖通道名
String channelType = resolveChannelType(cameraCode);
int typedChannel = cameraChannelByChannelType(channelType);
if (typedChannel > 0) {
log.info("[live] 按台账镜头类型分配相机通道 cameraCode={} channelType={} cameraChannel={}",
cameraCode, channelType, typedChannel);
return typedChannel;
}
int index = -1; int index = -1;
String cameraName = null; String cameraName = null;
// 通道记录按 code 升序返回包含当前通道自己 // 通道记录按 code 升序返回包含当前通道自己
@ -1390,39 +1419,90 @@ public class HikNvrLiveStreamService {
} }
} }
boolean reverse = "desc".equals(cameraChannelOrder); boolean reverse = "desc".equals(cameraChannelOrder);
// 先按名称判断镜头类型双光谱相机两个通道名通常能区分可见光/热成像
// 台账没有可用的镜头类型时退回名称判断
int namedChannel = cameraChannelByThermalName(cameraName); int namedChannel = cameraChannelByThermalName(cameraName);
if (namedChannel > 0) { if (namedChannel > 0) {
// 顺序反转时交换通道 1 与通道 2
return reverse ? (namedChannel == 1 ? 2 : 1) : namedChannel;
return namedChannel;
} }
if (index < 0) { if (index < 0) {
// 当前通道不在同 IP 列表中数据异常时退回相机通道 1 // 当前通道不在同 IP 列表中数据异常时退回相机通道 1
return 1; return 1;
} }
// 名称无法区分例如两个通道都叫双光谱时按平台通道顺序分配
// 名称无法区分例如两个通道都叫双光谱时按平台通道顺序分配
int ordered = reverse ? peers.size() - 1 - index : index; int ordered = reverse ? peers.size() - 1 - index : index;
return ordered + 1; return ordered + 1;
} }
/**
* 读取台账 basedata_eqpbook_channel.channel_type
*
* @param cameraCode IVS 完整摄像机编码
* @return 台账通道类型vl/ir 无记录字段为空或查询异常时返回 null
*/
private String resolveChannelType(String cameraCode) {
try {
String channelType = eqpbookChannelMapper.selectChannelTypeByChannelCode(cameraCode);
// 台账没有该编码时保持为空由调用方回退名称与顺序判定
return hasText(channelType) ? channelType.trim() : null;
} catch (RuntimeException queryFailure) {
// 台账不是取流必需数据查询失败只记录原因不阻断实时预览
log.warn("[live] 读取basedata_eqpbook_channel失败,回退名称判定 cameraCode={}:{}",
cameraCode, queryFailure.getMessage());
return null;
}
}
/**
* 按台账通道类型返回可见光/热成像对应的相机内通道号
*
* @param channelType 台账 channel_typevl 可见光 / ir 红外热成像
* @return {@code ir} 返回配置的相机内通道号{@code vl} 返回另一个通道无法判定返回 0
*/
private int cameraChannelByChannelType(String channelType) {
if (!hasText(channelType)) {
return 0;
}
String type = channelType.toLowerCase(Locale.ROOT);
if (CHANNEL_TYPE_INFRARED.equals(type)) {
return irCameraChannel;
}
if (CHANNEL_TYPE_VISIBLE.equals(type)) {
return otherCameraChannel(irCameraChannel);
}
// ptz/ng 等与镜头无关的类型不参与判定交给名称和顺序兜底
return 0;
}
/**
* 返回双光谱相机另一个镜头的相机内通道号
*
* @param cameraChannel 已确定的相机内通道号
* @return 双光谱相机只有两个镜头返回 1 2 中的另一个
*/
private int otherCameraChannel(int cameraChannel) {
return cameraChannel == 1 ? 2 : 1;
}
/** /**
* 按通道名判断镜头类型并返回对应的相机内通道号 * 按通道名判断镜头类型并返回对应的相机内通道号
* *
* <p>只在台账 {@code channel_type} 缺失时使用部分平台通道名带热成像/可见光
* 字样但名称由台账人员维护可能两个通道同名因此不作为首选依据</p>
*
* @param cameraName 平台通道名 * @param cameraName 平台通道名
* @return 热成像/测温返回 1可见光返回 2无法判断返回 0
* @return 热成像/测温返回配置的相机内通道号可见光返回另一个通道无法判断返回 0
*/ */
private int cameraChannelByThermalName(String cameraName) { private int cameraChannelByThermalName(String cameraName) {
if (!hasText(cameraName)) { if (!hasText(cameraName)) {
return 0; return 0;
} }
String name = cameraName.toLowerCase(Locale.ROOT); String name = cameraName.toLowerCase(Locale.ROOT);
// 海康双光谱相机默认 1xx 为热成像通道2xx 为可见光通道
if (name.contains("热成像") || name.contains("测温") || name.contains("thermal")) { if (name.contains("热成像") || name.contains("测温") || name.contains("thermal")) {
return 1;
return irCameraChannel;
} }
if (name.contains("可见光") || name.contains("optical") if (name.contains("可见光") || name.contains("optical")
|| name.contains("visual") || name.contains("白光")) { || name.contains("visual") || name.contains("白光")) {
return 2;
return otherCameraChannel(irCameraChannel);
} }
return 0; return 0;
} }


+ 15
- 2
src/main/java/com/inspect/nvr/hik/vo/HikNvrLiveStreamConfigVo.java View File

@ -82,12 +82,25 @@ public class HikNvrLiveStreamConfigVo {
/** 直连摄像机 RTSP 端口连通性探测超时时间。 */ /** 直连摄像机 RTSP 端口连通性探测超时时间。 */
@Value("${hik.playback.live.camera-probe-timeout-millis:500}") @Value("${hik.playback.live.camera-probe-timeout-millis:500}")
private int cameraProbeTimeoutMillis = 500; private int cameraProbeTimeoutMillis = 500;
/**
* 台账通道类型 {@code ir}红外热成像对应的相机内通道号
*
* <p>双光谱机身相机在相机内部占两个通道RTSP 地址的 trackId = 相机内通道 × 100
* + 码流类型台账 {@code basedata_eqpbook_channel.channel_type} {@code vl}
* 是可见光{@code ir} 是热成像本值决定 {@code ir} 使用哪个相机内通道
* 默认 2 1=可见光2=热成像{@code vl} 自动使用另一个通道</p>
*
* <p>现场若发现两个镜头画面互换把本值改成 1 即可台账判定与通道名判定会
* 一起反转不需要再动 {@code camera-channel-order}</p>
*/
@Value("${hik.playback.live.ir-camera-channel:2}")
private int irCameraChannel = 2;
/** /**
* 同一 IP 多通道相机的相机内通道分配顺序 * 同一 IP 多通道相机的相机内通道分配顺序
* *
* <p>{@code asc}默认按平台通道编码升序分配 1..N{@code desc} 反向 * <p>{@code asc}默认按平台通道编码升序分配 1..N{@code desc} 反向
* 双光谱相机优先按名称判断镜头热成像/测温 相机通道 1可见光 2
* 名称无法区分时才使用本顺序现场若发现两个镜头画面互换改成 {@code desc} 即可</p>
* 只在台账 {@code channel_type} 与平台通道名都判断不出可见光/热成像时才生效
* 镜头判定结果不由本配置调整改镜头方向请用 {@code ir-camera-channel}</p>
*/ */
@Value("${hik.playback.live.camera-channel-order:asc}") @Value("${hik.playback.live.camera-channel-order:asc}")
private String cameraChannelOrder = "asc"; private String cameraChannelOrder = "asc";


+ 25
- 0
src/main/java/com/inspect/nvr/ivs/mapper/BasedataEqpbookChannelMapper.java View File

@ -0,0 +1,25 @@
package com.inspect.nvr.ivs.mapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
/**
* 台账通道表 basedata_eqpbook_channel MyBatis Mapper
*
* <p>该表的 {@code channel_code} {@code ivs_camera.code} 同为
* 设备编码#域编码{@code channel_type} 是通道镜头类型的权威来源
* {@code vl} 可见光{@code ir} 红外热成像另有 {@code ptz} 云台
* {@code ng} 导航等与镜头无关的类型实时预览直连摄像机时用它判定双光谱
* 机身相机的镜头替代不受控的平台通道名关键字判断</p>
*/
@Mapper
public interface BasedataEqpbookChannelMapper {
/**
* IVS 完整摄像机编码读取台账通道类型
*
* @param channelCode IVS 完整摄像机编码 {@code ivs_camera.code} 同值
* @return 台账 channel_typevl/ir 无记录或字段为空时返回 null
*/
String selectChannelTypeByChannelCode(@Param("channelCode") String channelCode);
}

+ 5
- 2
src/main/resources/application.yml View File

@ -84,9 +84,12 @@ hik:
camera-password: "${HIK_NVR_LIVE_CAMERA_PASSWORD:}" camera-password: "${HIK_NVR_LIVE_CAMERA_PASSWORD:}"
# 直连摄像机 RTSP 端口连通性探测超时(毫秒)。 # 直连摄像机 RTSP 端口连通性探测超时(毫秒)。
camera-probe-timeout-millis: ${HIK_NVR_LIVE_CAMERA_PROBE_TIMEOUT_MILLIS:500} camera-probe-timeout-millis: ${HIK_NVR_LIVE_CAMERA_PROBE_TIMEOUT_MILLIS:500}
# 台账 basedata_eqpbook_channel.channel_type=ir(红外热成像)对应的相机内通道号。
# 台账里 vl=可见光、ir=热成像,是镜头类型的权威来源;默认 ir 用相机通道 2,
# 即 1=可见光、2=热成像。现场若两个镜头画面互换,只改这里即可整体反转。
ir-camera-channel: ${HIK_NVR_LIVE_IR_CAMERA_CHANNEL:2}
# 同一 IP 多通道相机(双光谱等)的相机内通道分配顺序:asc / desc。 # 同一 IP 多通道相机(双光谱等)的相机内通道分配顺序:asc / desc。
# 默认先按通道名判断镜头(热成像/测温 → 相机通道 1,可见光 → 2);
# 若现场发现某台双光相机的两个窗口画面互换,把这里改成 desc。
# 只在台账 channel_type 与平台通道名都判断不出镜头类型时才生效。
camera-channel-order: ${HIK_NVR_LIVE_CAMERA_CHANNEL_ORDER:asc} camera-channel-order: ${HIK_NVR_LIVE_CAMERA_CHANNEL_ORDER:asc}
clip: clip:
# 一分钟录像先由SDK下载到临时目录,再由FFmpeg统一封装为MP4。 # 一分钟录像先由SDK下载到临时目录,再由FFmpeg统一封装为MP4。


+ 21
- 0
src/main/resources/mapper/ivs/BasedataEqpbookChannelMapper.xml View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.inspect.nvr.ivs.mapper.BasedataEqpbookChannelMapper">
<!--
按完整摄像机编码读取台账镜头类型。channel_code 与 ivs_camera.code 同为
“设备编码#域编码”,台账中同一编码理论上只有一条有效记录;出现历史重复数据时
取最新的一条,避免旧记录决定镜头类型。命中 ivs_camera.code 一样的等值条件,
因此保留 channel_code 上的索引可用性,不做 TRIM 包装。
-->
<select id="selectChannelTypeByChannelCode" resultType="java.lang.String">
SELECT channel_type
FROM basedata_eqpbook_channel
WHERE channel_code = #{channelCode}
ORDER BY channel_id DESC
LIMIT 1
</select>
</mapper>

Loading…
Cancel
Save