|
|
|
@ -30,7 +30,9 @@ import com.inspect.nvr.hik.domain.HikNvrUiActionRequest; |
|
|
|
import com.inspect.nvr.hik.domain.HikNvrUiActionResponse; |
|
|
|
import com.inspect.nvr.hik.exception.HikNvrPlaybackException; |
|
|
|
import com.inspect.nvr.hik.service.HikNvrCameraTargetService; |
|
|
|
import com.inspect.nvr.ivs.mapper.IvsCameraMapper; |
|
|
|
import com.inspect.nvr.ivs.mapper.IvsNvrMapper; |
|
|
|
import com.inspect.nvr.ivs.pojo.IvsCamera; |
|
|
|
import com.inspect.nvr.ivs.pojo.IvsNvr; |
|
|
|
import com.inspect.nvr.ivs.service.IvsVendorAdapter; |
|
|
|
import com.inspect.nvr.service.DahuaCameraService; |
|
|
|
@ -108,6 +110,8 @@ public class DahuaIvsVendorService implements IvsVendorAdapter { |
|
|
|
private static final java.nio.charset.Charset PRESET_CONFIG_CHARSET = StandardCharsets.UTF_8; |
|
|
|
/** PtzPreset 原始 JSON 配置缓冲区大小,与大华官方工具类保持一致。 */ |
|
|
|
private static final int PRESET_CONFIG_BUFFER_SIZE = 2 * 1024 * 1024; |
|
|
|
/** 相机自身 RTSP 服务端口;直连相机不使用 NVR 转发端口配置。 */ |
|
|
|
private static final int CAMERA_RTSP_PORT = 554; |
|
|
|
|
|
|
|
/** 数据库安全的 cameraCode 到 NVR/通道解析服务。 */ |
|
|
|
@Resource |
|
|
|
@ -115,6 +119,9 @@ public class DahuaIvsVendorService implements IvsVendorAdapter { |
|
|
|
/** NVR 编码和厂商数据库访问。 */ |
|
|
|
@Resource |
|
|
|
private IvsNvrMapper nvrMapper; |
|
|
|
/** IVS 摄像机表 Mapper,用于按完整编码读取相机 IP 和同 IP 通道。 */ |
|
|
|
@Resource |
|
|
|
private IvsCameraMapper cameraMapper; |
|
|
|
/** 大华录像查询、回放和下载能力。 */ |
|
|
|
@Resource |
|
|
|
private DahuaNvrPlaybackService playbackService; |
|
|
|
@ -194,6 +201,84 @@ public class DahuaIvsVendorService implements IvsVendorAdapter { |
|
|
|
return IvsRtspUrlResponse.builder().resultCode(0).rtspURL(url).build(); |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 直接构造摄像机自身的大华实时 RTSP 地址,不经过所属 NVR 转发。 |
|
|
|
* |
|
|
|
* <p>与 v1.0 的差异:地址主机取自 {@code ivs_camera.dev_ip},而不是 NVR 的 IP; |
|
|
|
* 凭据沿用所属 NVR 的数据库记录,与海康 v2.0 的相机直连做法保持一致。 |
|
|
|
* 回放地址仍由 v1.0 的 NVR 转发路径提供。</p> |
|
|
|
*/ |
|
|
|
@Override |
|
|
|
public IvsRtspUrlResponse directRtspUrl(IvsVideoRtspVo request) { |
|
|
|
// 缺少摄像机编码时无法定位相机 IP 和所属 NVR 凭据。 |
|
|
|
if (request == null || !hasText(request.getCameraCode())) { |
|
|
|
throw HikNvrPlaybackException.badRequest("cameraCode不能为空"); |
|
|
|
} |
|
|
|
RealMediaURLParam media = request.getMediaURLParam(); |
|
|
|
if (media == null) { |
|
|
|
throw HikNvrPlaybackException.badRequest("mediaURLParam不能为空"); |
|
|
|
} |
|
|
|
int serviceType = media.getServiceType() == 0 ? 1 : media.getServiceType(); |
|
|
|
// 相机直连只提供实时预览地址,避免把回放请求错误地映射到实时流。 |
|
|
|
if (serviceType != 1) { |
|
|
|
throw HikNvrPlaybackException.badRequest( |
|
|
|
"相机直连RTSP仅支持实时预览serviceType=1"); |
|
|
|
} |
|
|
|
int streamType = media.getStreamType() == 0 ? 1 : media.getStreamType(); |
|
|
|
// 与 v1.0 保持一致,只支持主、辅两档码流。 |
|
|
|
if (streamType != 1 && streamType != 2) { |
|
|
|
throw HikNvrPlaybackException.badRequest("streamType只能为1或2"); |
|
|
|
} |
|
|
|
// 先按 cameraCode 解析所属大华 NVR,既校验厂商也取得服务端凭据。 |
|
|
|
HikNvrCameraTarget target = resolveTarget(request.getCameraCode(), null); |
|
|
|
NvrInfo nvr = target.getNvrInfo(); |
|
|
|
// 缺少库内账号或密码时无法建立相机 RTSP 会话,返回明确错误而不是拼出无效地址。 |
|
|
|
if (nvr == null || !hasText(nvr.getAccount()) || nvr.getPassword() == null) { |
|
|
|
throw HikNvrPlaybackException.notFound( |
|
|
|
"所属NVR未配置相机直连所需的账号或密码"); |
|
|
|
} |
|
|
|
IvsCamera camera = cameraMapper.selectByCode(target.getCameraCode()); |
|
|
|
if (camera == null || !hasText(camera.getDevIP())) { |
|
|
|
throw HikNvrPlaybackException.notFound( |
|
|
|
"未找到cameraCode对应的相机直连地址:" + request.getCameraCode()); |
|
|
|
} |
|
|
|
// 复用大华实时地址构造器,只把主机换成相机 IP、通道换成相机内通道。 |
|
|
|
HikNvrPlaybackRequest cameraRequest = HikNvrPlaybackRequest.builder() |
|
|
|
.nvrIp(camera.getDevIP().trim()) |
|
|
|
.rtspPort(CAMERA_RTSP_PORT) |
|
|
|
.username(nvr.getAccount()) |
|
|
|
.password(nvr.getPassword()) |
|
|
|
.channel(directCameraChannel(camera)) |
|
|
|
.streamType(streamType) |
|
|
|
.build(); |
|
|
|
return IvsRtspUrlResponse.builder() |
|
|
|
.resultCode(0) |
|
|
|
.rtspURL(playbackService.liveUrl(cameraRequest)) |
|
|
|
.build(); |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 根据同一 {@code dev_ip} 下的摄像机记录确定相机内通道,单镜头默认使用通道 1。 |
|
|
|
* |
|
|
|
* <p>双光谱等多镜头相机在平台上一个 IP 占多个通道,直连取流必须带上相机自己的 |
|
|
|
* 通道号,否则两个平台通道会拉到同一个镜头。</p> |
|
|
|
*/ |
|
|
|
private int directCameraChannel(IvsCamera camera) { |
|
|
|
List<IvsCamera> peers = cameraMapper.selectChannelPeersByDevIp( |
|
|
|
camera.getDevIP().trim()); |
|
|
|
if (peers == null || peers.size() <= 1) { |
|
|
|
return 1; |
|
|
|
} |
|
|
|
for (int index = 0; index < peers.size(); index++) { |
|
|
|
IvsCamera peer = peers.get(index); |
|
|
|
if (peer != null && camera.getCode().equalsIgnoreCase(peer.getCode())) { |
|
|
|
return index + 1; |
|
|
|
} |
|
|
|
} |
|
|
|
// 当前通道不在同 IP 列表中(数据异常)时退回相机通道 1。 |
|
|
|
return 1; |
|
|
|
} |
|
|
|
|
|
|
|
/** 使用大华 SDK 创建平台抓图任务。 */ |
|
|
|
@Override |
|
|
|
public IvsSnapshotCommandResponse platformSnapshot(IvsDevChanSnapVo request) { |
|
|
|
|