|
|
@ -13,6 +13,7 @@ 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.BasedataEqpbookChannelMapper; |
|
|
import com.inspect.nvr.ivs.mapper.IvsCameraMapper; |
|
|
import com.inspect.nvr.ivs.mapper.IvsCameraMapper; |
|
|
|
|
|
import com.inspect.nvr.ivs.pojo.BasedataEqpbookChannel; |
|
|
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; |
|
|
import com.inspect.nvr.service.HikLoginService; |
|
|
import com.inspect.nvr.service.HikLoginService; |
|
|
@ -36,6 +37,9 @@ import java.nio.file.Files; |
|
|
import java.nio.file.Path; |
|
|
import java.nio.file.Path; |
|
|
import java.nio.file.Paths; |
|
|
import java.nio.file.Paths; |
|
|
import java.util.ArrayList; |
|
|
import java.util.ArrayList; |
|
|
|
|
|
import java.util.Collections; |
|
|
|
|
|
import java.util.HashMap; |
|
|
|
|
|
import java.util.HashSet; |
|
|
import java.util.List; |
|
|
import java.util.List; |
|
|
import java.util.Locale; |
|
|
import java.util.Locale; |
|
|
import java.util.Map; |
|
|
import java.util.Map; |
|
|
@ -1328,15 +1332,21 @@ public class HikNvrLiveStreamService { |
|
|
} |
|
|
} |
|
|
// 没有设备 IP 时无法直连,保持原有 NVR 取流行为。 |
|
|
// 没有设备 IP 时无法直连,保持原有 NVR 取流行为。 |
|
|
if (camera == null || !hasText(camera.getDevIP())) { |
|
|
if (camera == null || !hasText(camera.getDevIP())) { |
|
|
|
|
|
log.info("[live] 跳过相机直连 cameraCode={} 原因=ivs_camera未配置dev_ip", |
|
|
|
|
|
target.getCameraCode()); |
|
|
return null; |
|
|
return null; |
|
|
} |
|
|
} |
|
|
String devIp = camera.getDevIP().trim(); |
|
|
String devIp = camera.getDevIP().trim(); |
|
|
// 已确认该相机凭据不可用时不再重复尝试直连。 |
|
|
// 已确认该相机凭据不可用时不再重复尝试直连。 |
|
|
if (cameraDirectAuthFailed.contains(devIp)) { |
|
|
if (cameraDirectAuthFailed.contains(devIp)) { |
|
|
|
|
|
log.info("[live] 跳过相机直连 cameraCode={} devIp={} 原因=该相机凭据此前鉴权失败", |
|
|
|
|
|
target.getCameraCode(), devIp); |
|
|
return null; |
|
|
return null; |
|
|
} |
|
|
} |
|
|
// RTSP 端口不可达(未开放或被网络隔离)时直接回退,避免白等一次尝试。 |
|
|
// RTSP 端口不可达(未开放或被网络隔离)时直接回退,避免白等一次尝试。 |
|
|
if (!isCameraRtspReachable(devIp)) { |
|
|
if (!isCameraRtspReachable(devIp)) { |
|
|
|
|
|
log.info("[live] 跳过相机直连 cameraCode={} devIp={} 原因=RTSP端口{}不可达", |
|
|
|
|
|
target.getCameraCode(), devIp, cameraRtspPort); |
|
|
return null; |
|
|
return null; |
|
|
} |
|
|
} |
|
|
NvrInfo nvrInfo = target.getNvrInfo(); |
|
|
NvrInfo nvrInfo = target.getNvrInfo(); |
|
|
@ -1346,6 +1356,8 @@ public class HikNvrLiveStreamService { |
|
|
? cameraPassword : nvrInfo.getPassword(); |
|
|
? cameraPassword : nvrInfo.getPassword(); |
|
|
// 账号缺失时无法建立 RTSP 会话,回退 NVR 路径由 IVS 侧给出明确错误。 |
|
|
// 账号缺失时无法建立 RTSP 会话,回退 NVR 路径由 IVS 侧给出明确错误。 |
|
|
if (!hasText(username) || password == null) { |
|
|
if (!hasText(username) || password == null) { |
|
|
|
|
|
log.info("[live] 跳过相机直连 cameraCode={} devIp={} 原因=缺少账号或密码", |
|
|
|
|
|
target.getCameraCode(), devIp); |
|
|
return null; |
|
|
return null; |
|
|
} |
|
|
} |
|
|
// 复用统一 URL 构造器,保证中文或特殊字符账号的百分号编码规则与 NVR 路径一致。 |
|
|
// 复用统一 URL 构造器,保证中文或特殊字符账号的百分号编码规则与 NVR 路径一致。 |
|
|
@ -1367,19 +1379,25 @@ public class HikNvrLiveStreamService { |
|
|
* 计算平台通道对应的相机内通道号。 |
|
|
* 计算平台通道对应的相机内通道号。 |
|
|
* |
|
|
* |
|
|
* <p>同一台多镜头相机(双光谱:可见光 + 热成像)在平台上占多个通道,直连 RTSP 时 |
|
|
* <p>同一台多镜头相机(双光谱:可见光 + 热成像)在平台上占多个通道,直连 RTSP 时 |
|
|
* 必须带上相机自己的通道号,否则两个平台通道会拉到同一个镜头。判定顺序:</p> |
|
|
|
|
|
|
|
|
* 必须带上相机自己的通道号,否则两个平台通道会拉到同一个镜头。台账与通道名都只 |
|
|
|
|
|
* 描述单个通道的镜头类型,必须先确认它们确实把同一 IP 下的兄弟通道区分开了才可用, |
|
|
|
|
|
* 否则两个平台通道会一起被映射到同一个相机通道。判定顺序:</p> |
|
|
* <ol> |
|
|
* <ol> |
|
|
* <li>台账 {@code basedata_eqpbook_channel.channel_type},{@code vl} 可见光、 |
|
|
* <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> |
|
|
|
|
|
|
|
|
* {@code ir} 红外热成像。{@code ir} 用的相机内通道号由 |
|
|
|
|
|
* {@code hik.playback.live.ir-camera-channel} 指定(默认 2,即 1=可见光、 |
|
|
|
|
|
* 2=热成像),{@code vl} 用另一个通道。台账只有在确实把同一 IP 下的兄弟通道 |
|
|
|
|
|
* 区分成不同镜头时才生效,见 {@link #resolveLedgerChannel};</li> |
|
|
|
|
|
* <li>台账缺失、类型与镜头无关(ptz/ng 等)或不足以区分镜头时,按平台通道名 |
|
|
|
|
|
* 关键字判断,名称同样要能区分镜头才生效,见 {@link #resolveNamedChannel};</li> |
|
|
|
|
|
* <li>上述信号都判断不出镜头类型时按平台通道编码顺序分配 1..N,整站顺序相反时用 |
|
|
|
|
|
* {@code hik.playback.live.camera-channel-order=desc} 反转。顺序分配天然让 |
|
|
|
|
|
* 兄弟通道落在不同的相机通道上,默认升序即多数现场的 1=可见光、2=热成像。</li> |
|
|
* </ol> |
|
|
* </ol> |
|
|
* |
|
|
* |
|
|
* <p>只有同 IP 多通道(真双光谱机身机)才做镜头判定:单通道设备的相机内通道号 |
|
|
* <p>只有同 IP 多通道(真双光谱机身机)才做镜头判定:单通道设备的相机内通道号 |
|
|
* 恒为 1,单镜头热成像相机套用镜头映射反而会取到不存在的通道。</p> |
|
|
|
|
|
|
|
|
* 恒为 1,单镜头热成像相机套用镜头映射反而会取到不存在的通道,因此单通道判断 |
|
|
|
|
|
* 排在所有镜头映射之前。</p> |
|
|
* |
|
|
* |
|
|
* @param cameraCode 当前平台通道编码 |
|
|
* @param cameraCode 当前平台通道编码 |
|
|
* @param devIp 摄像机 IP |
|
|
* @param devIp 摄像机 IP |
|
|
@ -1395,32 +1413,28 @@ 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; |
|
|
|
|
|
// 通道记录按 code 升序返回,包含当前通道自己。 |
|
|
// 通道记录按 code 升序返回,包含当前通道自己。 |
|
|
for (int position = 0; position < peers.size(); position++) { |
|
|
for (int position = 0; position < peers.size(); position++) { |
|
|
IvsCamera peer = peers.get(position); |
|
|
IvsCamera peer = peers.get(position); |
|
|
if (peer != null && cameraCode.equalsIgnoreCase(peer.getCode())) { |
|
|
if (peer != null && cameraCode.equalsIgnoreCase(peer.getCode())) { |
|
|
index = position; |
|
|
index = position; |
|
|
cameraName = peer.getName(); |
|
|
|
|
|
break; |
|
|
break; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
boolean reverse = "desc".equals(cameraChannelOrder); |
|
|
|
|
|
// 台账没有可用的镜头类型时退回名称判断。 |
|
|
|
|
|
int namedChannel = cameraChannelByThermalName(cameraName); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 1.同 IP 多通道说明是多镜头机身相机,先按台账镜头类型判定,避免依赖通道名。 |
|
|
|
|
|
int ledgerChannel = resolveLedgerChannel(peers, index); |
|
|
|
|
|
if (ledgerChannel > 0) { |
|
|
|
|
|
return ledgerChannel; |
|
|
|
|
|
} |
|
|
|
|
|
// 2.台账不具备区分镜头的能力时退回名称判断。 |
|
|
|
|
|
int namedChannel = resolveNamedChannel(peers, index); |
|
|
if (namedChannel > 0) { |
|
|
if (namedChannel > 0) { |
|
|
return namedChannel; |
|
|
return namedChannel; |
|
|
} |
|
|
} |
|
|
@ -1428,33 +1442,147 @@ public class HikNvrLiveStreamService { |
|
|
// 当前通道不在同 IP 列表中(数据异常)时退回相机通道 1。 |
|
|
// 当前通道不在同 IP 列表中(数据异常)时退回相机通道 1。 |
|
|
return 1; |
|
|
return 1; |
|
|
} |
|
|
} |
|
|
// 名称也无法区分(例如两个通道都叫“双光谱”)时按平台通道顺序分配。 |
|
|
|
|
|
int ordered = reverse ? peers.size() - 1 - index : index; |
|
|
|
|
|
return ordered + 1; |
|
|
|
|
|
|
|
|
// 3.名称也无法区分(例如两个通道都叫“双光谱”,或都是可见光)时按平台通道顺序分配。 |
|
|
|
|
|
boolean reverse = "desc".equals(cameraChannelOrder); |
|
|
|
|
|
int orderedChannel = (reverse ? peers.size() - 1 - index : index) + 1; |
|
|
|
|
|
log.info("[live] 台账与通道名都未区分镜头,按平台通道顺序分配相机通道" |
|
|
|
|
|
+ " cameraCode={} index={}/{} order={} cameraChannel={}", |
|
|
|
|
|
cameraCode, index, peers.size(), cameraChannelOrder, orderedChannel); |
|
|
|
|
|
return orderedChannel; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* 读取台账 basedata_eqpbook_channel.channel_type。 |
|
|
|
|
|
|
|
|
* 按台账镜头类型判定当前通道的相机内通道号。 |
|
|
* |
|
|
* |
|
|
* @param cameraCode IVS 完整摄像机编码 |
|
|
|
|
|
* @return 台账通道类型(vl/ir 等);无记录、字段为空或查询异常时返回 null |
|
|
|
|
|
|
|
|
* <p>台账能用来判定镜头的前提,是它确实把同一 IP 下的兄弟通道区分成了不同镜头。 |
|
|
|
|
|
* 现场既有“1=可见光、2=热成像”的双光谱相机,也有两个镜头都是可见光、台账两个 |
|
|
|
|
|
* 通道都记为 {@code vl} 的机身机;后者逐通道套用类型会让两个平台通道解析到同一个 |
|
|
|
|
|
* 相机通道,两个窗口拉到同一个镜头。因此这里先统计兄弟通道按台账解析出的相机 |
|
|
|
|
|
* 通道,只有一个取值时视为台账不具备区分能力,返回 0 交给通道名与平台通道顺序 |
|
|
|
|
|
* 兜底,由顺序分配把两个通道分别落到相机通道 1 和 2:双可见光相机两路各自取到 |
|
|
|
|
|
* 一个可见光镜头,双光谱相机则分别取到可见光与热成像镜头。</p> |
|
|
|
|
|
* |
|
|
|
|
|
* @param peers 同一 IP 下的平台通道,按完整编码升序 |
|
|
|
|
|
* @param index 当前通道在 {@code peers} 中的下标,-1 表示不在列表中 |
|
|
|
|
|
* @return 台账判定的相机内通道号;台账不具备区分能力时返回 0 |
|
|
|
|
|
*/ |
|
|
|
|
|
private int resolveLedgerChannel(List<IvsCamera> peers, int index) { |
|
|
|
|
|
if (index < 0) { |
|
|
|
|
|
return 0; |
|
|
|
|
|
} |
|
|
|
|
|
// 同一ip下的key:channelCode,value:channelType |
|
|
|
|
|
Map<String, String> channelTypes = loadChannelTypes(peers); |
|
|
|
|
|
Set<Integer> resolvedChannels = new HashSet<>(peers.size()); |
|
|
|
|
|
for (IvsCamera peer : peers) { |
|
|
|
|
|
if (peer == null) { |
|
|
|
|
|
continue; |
|
|
|
|
|
} |
|
|
|
|
|
int peerChannel = cameraChannelByChannelType(channelTypes.get(peer.getCode())); |
|
|
|
|
|
if (peerChannel > 0) { |
|
|
|
|
|
resolvedChannels.add(peerChannel); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
// 兄弟通道全部落在同一个相机通道上,说明台账没有区分镜头,不能据此分配。 |
|
|
|
|
|
if (resolvedChannels.size() <= 1) { |
|
|
|
|
|
return 0; |
|
|
|
|
|
} |
|
|
|
|
|
String cameraCode = peers.get(index).getCode(); |
|
|
|
|
|
String channelType = channelTypes.get(cameraCode); |
|
|
|
|
|
int typedChannel = cameraChannelByChannelType(channelType); |
|
|
|
|
|
if (typedChannel > 0) { |
|
|
|
|
|
log.info("[live] 按台账镜头类型分配相机通道 cameraCode={} channelType={} cameraChannel={}", |
|
|
|
|
|
cameraCode, channelType, typedChannel); |
|
|
|
|
|
} |
|
|
|
|
|
return typedChannel; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 按平台通道名判定当前通道的相机内通道号。 |
|
|
|
|
|
* |
|
|
|
|
|
* <p>与台账一样必须先确认信号能区分镜头:两个通道都带“可见光”的相机(双可见光 |
|
|
|
|
|
* 机身机,或热成像那一路名称没标注)逐个通道套用关键字会让两个平台通道落到同一个 |
|
|
|
|
|
* 相机通道。因此兄弟通道名称解析出的相机通道只有一个取值时返回 0,交给平台通道 |
|
|
|
|
|
* 顺序兜底。</p> |
|
|
|
|
|
* |
|
|
|
|
|
* @param peers 同一 IP 下的平台通道,按完整编码升序 |
|
|
|
|
|
* @param index 当前通道在 {@code peers} 中的下标,-1 表示不在列表中 |
|
|
|
|
|
* @return 名称判定的相机内通道号;名称不足以区分镜头时返回 0 |
|
|
*/ |
|
|
*/ |
|
|
private String resolveChannelType(String cameraCode) { |
|
|
|
|
|
|
|
|
private int resolveNamedChannel(List<IvsCamera> peers, int index) { |
|
|
|
|
|
if (index < 0) { |
|
|
|
|
|
return 0; |
|
|
|
|
|
} |
|
|
|
|
|
Set<Integer> resolvedChannels = new HashSet<>(peers.size()); |
|
|
|
|
|
for (IvsCamera peer : peers) { |
|
|
|
|
|
if (peer == null) { |
|
|
|
|
|
continue; |
|
|
|
|
|
} |
|
|
|
|
|
int peerChannel = cameraChannelByThermalName(peer.getName()); |
|
|
|
|
|
if (peerChannel > 0) { |
|
|
|
|
|
resolvedChannels.add(peerChannel); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
// 各通道名称指向同一个镜头,说明名称没有区分镜头,不能据此分配。 |
|
|
|
|
|
if (resolvedChannels.size() <= 1) { |
|
|
|
|
|
return 0; |
|
|
|
|
|
} |
|
|
|
|
|
IvsCamera current = peers.get(index); |
|
|
|
|
|
int namedChannel = cameraChannelByThermalName(current.getName()); |
|
|
|
|
|
if (namedChannel > 0) { |
|
|
|
|
|
log.info("[live] 按通道名分配相机通道 cameraCode={} cameraName={} cameraChannel={}", |
|
|
|
|
|
current.getCode(), current.getName(), namedChannel); |
|
|
|
|
|
} |
|
|
|
|
|
return namedChannel; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 批量读取同一 IP 下各平台通道的台账镜头类型。 |
|
|
|
|
|
* |
|
|
|
|
|
* @param peers 同一 IP 下的平台通道 |
|
|
|
|
|
* @return 摄像机编码到台账 channel_type 的映射;同一编码有多条历史台账时取最新一条, |
|
|
|
|
|
* 无记录或查询异常时该编码不出现在映射中 |
|
|
|
|
|
*/ |
|
|
|
|
|
private Map<String, String> loadChannelTypes(List<IvsCamera> peers) { |
|
|
|
|
|
List<String> codes = new ArrayList<>(peers.size()); |
|
|
|
|
|
for (IvsCamera peer : peers) { |
|
|
|
|
|
if (peer != null && hasText(peer.getCode())) { |
|
|
|
|
|
codes.add(peer.getCode()); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
if (codes.isEmpty()) { |
|
|
|
|
|
return Collections.emptyMap(); |
|
|
|
|
|
} |
|
|
|
|
|
Map<String, String> channelTypes = new HashMap<>(codes.size()); |
|
|
try { |
|
|
try { |
|
|
String channelType = eqpbookChannelMapper.selectChannelTypeByChannelCode(cameraCode); |
|
|
|
|
|
// 台账没有该编码时保持为空,由调用方回退名称与顺序判定。 |
|
|
|
|
|
return hasText(channelType) ? channelType.trim() : null; |
|
|
|
|
|
|
|
|
List<BasedataEqpbookChannel> records = |
|
|
|
|
|
eqpbookChannelMapper.selectChannelTypesByChannelCodes(codes); |
|
|
|
|
|
for (BasedataEqpbookChannel record : records) { |
|
|
|
|
|
if (record == null || !hasText(record.getChannelCode())) { |
|
|
|
|
|
continue; |
|
|
|
|
|
} |
|
|
|
|
|
// 同一编码存在历史重复台账时查询按 channel_id 倒序返回,保留首条即最新记录。 |
|
|
|
|
|
if (channelTypes.containsKey(record.getChannelCode())) { |
|
|
|
|
|
continue; |
|
|
|
|
|
} |
|
|
|
|
|
channelTypes.put(record.getChannelCode(), |
|
|
|
|
|
hasText(record.getChannelType()) ? record.getChannelType().trim() : null); |
|
|
|
|
|
} |
|
|
} catch (RuntimeException queryFailure) { |
|
|
} catch (RuntimeException queryFailure) { |
|
|
// 台账不是取流必需数据,查询失败只记录原因,不阻断实时预览。 |
|
|
// 台账不是取流必需数据,查询失败只记录原因,不阻断实时预览。 |
|
|
log.warn("[live] 读取basedata_eqpbook_channel失败,回退名称判定 cameraCode={}:{}", |
|
|
|
|
|
cameraCode, queryFailure.getMessage()); |
|
|
|
|
|
return null; |
|
|
|
|
|
|
|
|
log.warn("[live] 读取basedata_eqpbook_channel失败,回退通道名判定 同IP通道数={}:{}", |
|
|
|
|
|
codes.size(), queryFailure.getMessage()); |
|
|
|
|
|
return Collections.emptyMap(); |
|
|
} |
|
|
} |
|
|
|
|
|
return channelTypes; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* 按台账通道类型返回可见光/热成像对应的相机内通道号。 |
|
|
* 按台账通道类型返回可见光/热成像对应的相机内通道号。 |
|
|
* |
|
|
* |
|
|
|
|
|
* <p>只做单点映射,不判断台账是否真的区分了兄弟通道:两个通道都记 {@code vl} 时 |
|
|
|
|
|
* 会一起落到同一个相机通道。因此调用方必须先确认台账具备区分能力,见 |
|
|
|
|
|
* {@link #resolveLedgerChannel}。</p> |
|
|
|
|
|
* |
|
|
* @param channelType 台账 channel_type,vl 可见光 / ir 红外热成像 |
|
|
* @param channelType 台账 channel_type,vl 可见光 / ir 红外热成像 |
|
|
* @return {@code ir} 返回配置的相机内通道号,{@code vl} 返回另一个通道;无法判定返回 0 |
|
|
* @return {@code ir} 返回配置的相机内通道号,{@code vl} 返回另一个通道;无法判定返回 0 |
|
|
*/ |
|
|
*/ |
|
|
@ -1486,8 +1614,9 @@ public class HikNvrLiveStreamService { |
|
|
/** |
|
|
/** |
|
|
* 按通道名判断镜头类型并返回对应的相机内通道号。 |
|
|
* 按通道名判断镜头类型并返回对应的相机内通道号。 |
|
|
* |
|
|
* |
|
|
* <p>只在台账 {@code channel_type} 缺失时使用:部分平台通道名带“热成像/可见光” |
|
|
|
|
|
* 字样,但名称由台账人员维护、可能两个通道同名,因此不作为首选依据。</p> |
|
|
|
|
|
|
|
|
* <p>只在台账 {@code channel_type} 缺失或不具备区分能力时使用:部分平台通道名带 |
|
|
|
|
|
* “热成像/可见光”字样,但名称由台账人员维护、可能两个通道同名。只做单点映射, |
|
|
|
|
|
* 调用方必须先确认名称能区分兄弟通道,见 {@link #resolveNamedChannel}。</p> |
|
|
* |
|
|
* |
|
|
* @param cameraName 平台通道名 |
|
|
* @param cameraName 平台通道名 |
|
|
* @return 热成像/测温返回配置的相机内通道号,可见光返回另一个通道,无法判断返回 0 |
|
|
* @return 热成像/测温返回配置的相机内通道号,可见光返回另一个通道,无法判断返回 0 |
|
|
@ -1569,9 +1698,9 @@ public class HikNvrLiveStreamService { |
|
|
command.add("error"); |
|
|
command.add("error"); |
|
|
command.add("-nostats"); |
|
|
command.add("-nostats"); |
|
|
// 海康 RTSP 不在 SDP 中给出帧率,ffmpeg 会自愿读满默认 5 秒探测窗口来估帧率; |
|
|
// 海康 RTSP 不在 SDP 中给出帧率,ffmpeg 会自愿读满默认 5 秒探测窗口来估帧率; |
|
|
// 显式压到 3 秒让推流提前约 2 秒开始,否则首次尝试 5 秒预算会在探测完成前杀掉进程。 |
|
|
|
|
|
|
|
|
// 压到 1.5 秒让推流提前约 3.5 秒开始:25fps 子码流下约 37 帧已足够估准帧率。 |
|
|
command.add("-analyzeduration"); |
|
|
command.add("-analyzeduration"); |
|
|
command.add("3000000"); |
|
|
|
|
|
|
|
|
command.add("1500000"); |
|
|
command.add("-rtsp_transport"); |
|
|
command.add("-rtsp_transport"); |
|
|
command.add("tcp"); |
|
|
command.add("tcp"); |
|
|
command.add("-i"); |
|
|
command.add("-i"); |
|
|
|