From 6ae9d664302ec6a8ee0aff463e0ec8e75e6246d5 Mon Sep 17 00:00:00 2001
From: WangGuangYuan 同一台多镜头相机(双光谱:可见光 + 热成像)在平台上占多个通道,直连 RTSP 时
- * 必须带上相机自己的通道号,否则两个平台通道会拉到同一个镜头。优先按平台通道名判断
- * 镜头类型(热成像/测温 → 相机通道 1,可见光 → 相机通道 2,与海康双光谱默认一致),
- * 名称无法区分时按平台通道编码顺序分配 1..N;整站顺序相反时用
- * {@code hik.playback.live.camera-channel-order=desc} 反转。
只有同 IP 多通道(真双光谱机身机)才做镜头判定:单通道设备的相机内通道号 + * 恒为 1,单镜头热成像相机套用镜头映射反而会取到不存在的通道。
* * @param cameraCode 当前平台通道编码 * @param devIp 摄像机 IP @@ -1374,10 +1395,18 @@ public class HikNvrLiveStreamService { devIp, queryFailure.getMessage()); return 1; } - // 单通道相机(绝大多数)直接使用相机通道 1。 + // 单通道相机(绝大多数)直接使用相机通道 1,不必再查台账。 if (peers == null || peers.size() <= 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; String cameraName = null; // 通道记录按 code 升序返回,包含当前通道自己。 @@ -1390,39 +1419,90 @@ public class HikNvrLiveStreamService { } } boolean reverse = "desc".equals(cameraChannelOrder); - // 先按名称判断镜头类型:双光谱相机两个通道名通常能区分可见光/热成像。 + // 台账没有可用的镜头类型时退回名称判断。 int namedChannel = cameraChannelByThermalName(cameraName); if (namedChannel > 0) { - // 顺序反转时交换通道 1 与通道 2。 - return reverse ? (namedChannel == 1 ? 2 : 1) : namedChannel; + return namedChannel; } if (index < 0) { // 当前通道不在同 IP 列表中(数据异常)时退回相机通道 1。 return 1; } - // 名称无法区分(例如两个通道都叫“双光谱”)时按平台通道顺序分配。 + // 名称也无法区分(例如两个通道都叫“双光谱”)时按平台通道顺序分配。 int ordered = reverse ? peers.size() - 1 - index : index; 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_type,vl 可见光 / 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; + } + /** * 按通道名判断镜头类型并返回对应的相机内通道号。 * + *只在台账 {@code channel_type} 缺失时使用:部分平台通道名带“热成像/可见光” + * 字样,但名称由台账人员维护、可能两个通道同名,因此不作为首选依据。
+ * * @param cameraName 平台通道名 - * @return 热成像/测温返回 1,可见光返回 2,无法判断返回 0 + * @return 热成像/测温返回配置的相机内通道号,可见光返回另一个通道,无法判断返回 0 */ private int cameraChannelByThermalName(String cameraName) { if (!hasText(cameraName)) { return 0; } String name = cameraName.toLowerCase(Locale.ROOT); - // 海康双光谱相机默认 1xx 为热成像通道、2xx 为可见光通道。 if (name.contains("热成像") || name.contains("测温") || name.contains("thermal")) { - return 1; + return irCameraChannel; } if (name.contains("可见光") || name.contains("optical") || name.contains("visual") || name.contains("白光")) { - return 2; + return otherCameraChannel(irCameraChannel); } return 0; } diff --git a/src/main/java/com/inspect/nvr/hik/vo/HikNvrLiveStreamConfigVo.java b/src/main/java/com/inspect/nvr/hik/vo/HikNvrLiveStreamConfigVo.java index e0f16a51..5b1946b4 100644 --- a/src/main/java/com/inspect/nvr/hik/vo/HikNvrLiveStreamConfigVo.java +++ b/src/main/java/com/inspect/nvr/hik/vo/HikNvrLiveStreamConfigVo.java @@ -82,12 +82,25 @@ public class HikNvrLiveStreamConfigVo { /** 直连摄像机 RTSP 端口连通性探测超时时间。 */ @Value("${hik.playback.live.camera-probe-timeout-millis:500}") private int cameraProbeTimeoutMillis = 500; + /** + * 台账通道类型 {@code ir}(红外热成像)对应的相机内通道号。 + * + *双光谱机身相机在相机内部占两个通道,RTSP 地址的 trackId = 相机内通道 × 100 + * + 码流类型。台账 {@code basedata_eqpbook_channel.channel_type} 中 {@code vl} + * 是可见光、{@code ir} 是热成像,本值决定 {@code ir} 使用哪个相机内通道: + * 默认 2,即 1=可见光、2=热成像;{@code vl} 自动使用另一个通道。
+ * + *现场若发现两个镜头画面互换,把本值改成 1 即可,台账判定与通道名判定会 + * 一起反转,不需要再动 {@code camera-channel-order}。
+ */ + @Value("${hik.playback.live.ir-camera-channel:2}") + private int irCameraChannel = 2; /** * 同一 IP 多通道相机的相机内通道分配顺序。 * *{@code asc}(默认)按平台通道编码升序分配 1..N,{@code desc} 反向。 - * 双光谱相机优先按名称判断镜头(热成像/测温 → 相机通道 1,可见光 → 2), - * 名称无法区分时才使用本顺序;现场若发现两个镜头画面互换,改成 {@code desc} 即可。
+ * 只在台账 {@code channel_type} 与平台通道名都判断不出可见光/热成像时才生效; + * 镜头判定结果不由本配置调整,改镜头方向请用 {@code ir-camera-channel}。 */ @Value("${hik.playback.live.camera-channel-order:asc}") private String cameraChannelOrder = "asc"; diff --git a/src/main/java/com/inspect/nvr/ivs/mapper/BasedataEqpbookChannelMapper.java b/src/main/java/com/inspect/nvr/ivs/mapper/BasedataEqpbookChannelMapper.java new file mode 100644 index 00000000..fdfb59ef --- /dev/null +++ b/src/main/java/com/inspect/nvr/ivs/mapper/BasedataEqpbookChannelMapper.java @@ -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。 + * + *该表的 {@code channel_code} 与 {@code ivs_camera.code} 同为 + * “设备编码#域编码”,{@code channel_type} 是通道镜头类型的权威来源: + * {@code vl} 可见光、{@code ir} 红外(热成像),另有 {@code ptz} 云台、 + * {@code ng} 导航等与镜头无关的类型。实时预览直连摄像机时用它判定双光谱 + * 机身相机的镜头,替代不受控的平台通道名关键字判断。
+ */ +@Mapper +public interface BasedataEqpbookChannelMapper { + + /** + * 按 IVS 完整摄像机编码读取台账通道类型。 + * + * @param channelCode IVS 完整摄像机编码,与 {@code ivs_camera.code} 同值 + * @return 台账 channel_type(vl/ir 等);无记录或字段为空时返回 null + */ + String selectChannelTypeByChannelCode(@Param("channelCode") String channelCode); +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 4e381efa..4629b968 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -84,9 +84,12 @@ hik: camera-password: "${HIK_NVR_LIVE_CAMERA_PASSWORD:}" # 直连摄像机 RTSP 端口连通性探测超时(毫秒)。 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。 - # 默认先按通道名判断镜头(热成像/测温 → 相机通道 1,可见光 → 2); - # 若现场发现某台双光相机的两个窗口画面互换,把这里改成 desc。 + # 只在台账 channel_type 与平台通道名都判断不出镜头类型时才生效。 camera-channel-order: ${HIK_NVR_LIVE_CAMERA_CHANNEL_ORDER:asc} clip: # 一分钟录像先由SDK下载到临时目录,再由FFmpeg统一封装为MP4。 diff --git a/src/main/resources/mapper/ivs/BasedataEqpbookChannelMapper.xml b/src/main/resources/mapper/ivs/BasedataEqpbookChannelMapper.xml new file mode 100644 index 00000000..a37224df --- /dev/null +++ b/src/main/resources/mapper/ivs/BasedataEqpbookChannelMapper.xml @@ -0,0 +1,21 @@ + + +