diff --git a/.gitignore b/.gitignore index 3a89eb0..ff9caa7 100644 --- a/.gitignore +++ b/.gitignore @@ -42,7 +42,7 @@ tables/ *.pprof *.test snap/* - +*buf264/* # Logs logs *.log diff --git a/configs/config.toml b/configs/config.toml index 3d5255f..284387c 100644 --- a/configs/config.toml +++ b/configs/config.toml @@ -37,7 +37,7 @@ # 连续分析帧数(2-64), 默认为10, 最大为 64 FrmNum = 10 # 是否使用深度学习版本, 默认使用深度学习版本 - IsDeepLearn = false + IsDeepLearn = true [VqdLgtDark] # 默认 0.4, 取值范围: 0~1, 建议范围: 0.2~0.6 diff --git a/internal/conf/config.go b/internal/conf/config.go index 69ed3f3..b631202 100644 --- a/internal/conf/config.go +++ b/internal/conf/config.go @@ -111,69 +111,69 @@ type VqdConfig struct { // 亮度检测 type VqdLgtDark struct { - DarkThr float64 `json:"dark_thr" comment:"默认 0.4, 取值范围: 0~1, 建议范围: 0.2~0.6"` - LgtThr float64 `json:"lgt_thr" comment:"默认 0.1, 取值范围: 0~1, 建议范围: 0.1~0.5"` - LgtDarkAbnNumRatio float64 `json:"lgt_dark_abn_num_ratio" comment:"默认为0.5, 取值范围: 0~1, 建议范围: 0.1~0.9"` + DarkThr float32 `json:"dark_thr" comment:"默认 0.4, 取值范围: 0~1, 建议范围: 0.2~0.6"` + LgtThr float32 `json:"lgt_thr" comment:"默认 0.1, 取值范围: 0~1, 建议范围: 0.1~0.5"` + LgtDarkAbnNumRatio float32 `json:"lgt_dark_abn_num_ratio" comment:"默认为0.5, 取值范围: 0~1, 建议范围: 0.1~0.9"` } // 蓝屏检查 type VqdBlue struct { - BlueThr float64 `json:"blue_thr" comment:"默认为 0.6, 取值范围: 0~1, 建议范围 0.4~0.9"` - BlueAbnNumRatio float64 `json:"blue_abn_num_ratio" comment:"默认为0.5, 取值范围: 0~1, 建议范围: 0.1~0.9"` + BlueThr float32 `json:"blue_thr" comment:"默认为 0.6, 取值范围: 0~1, 建议范围 0.4~0.9"` + BlueAbnNumRatio float32 `json:"blue_abn_num_ratio" comment:"默认为0.5, 取值范围: 0~1, 建议范围: 0.1~0.9"` } // 清晰度检查 type VqdClarity struct { - ClarityThr float64 `json:"clarity_thr" comment:"默认为0.4, 取值范围: 0~1, 建议范围: 0.3~0.99"` - ClarityAbnNumRatio float64 `json:"clarity_abn_num_ratio" comment:"默认为0.5, 取值范围: 0~1, 建议范围: 0.1~0.9"` + ClarityThr float32 `json:"clarity_thr" comment:"默认为0.4, 取值范围: 0~1, 建议范围: 0.3~0.99"` + ClarityAbnNumRatio float32 `json:"clarity_abn_num_ratio" comment:"默认为0.5, 取值范围: 0~1, 建议范围: 0.1~0.9"` } // 抖动检查 type VqdShark struct { - SharkThr float64 `json:"shark_thr" comment:"默认为 0.2, 取值范围: 0~1, 建议范围: 0.1~0.8"` - SharkAbnNumRatio float64 `json:"shark_abn_num_ratio" comment:"默认为0.2, 取值范围: 0~1, 建议范围: 0.1~0.6"` + SharkThr float32 `json:"shark_thr" comment:"默认为 0.2, 取值范围: 0~1, 建议范围: 0.1~0.8"` + SharkAbnNumRatio float32 `json:"shark_abn_num_ratio" comment:"默认为0.2, 取值范围: 0~1, 建议范围: 0.1~0.6"` } // 冻结检测 type VqdFreeze struct { - FreezeThr float64 `json:"freeze_thr" comment:"默认 0.4, 取值范围: 0~1, 建议范围: 0.2~0.6"` - FreezeAbnNumRatio float64 `json:"freeze_abn_num_ratio" comment:"默认为0.99, 取值范围: 0.8~1, 建议范围: 0.95~1"` + FreezeThr float32 `json:"freeze_thr" comment:"默认 0.4, 取值范围: 0~1, 建议范围: 0.2~0.6"` + FreezeAbnNumRatio float32 `json:"freeze_abn_num_ratio" comment:"默认为0.99, 取值范围: 0.8~1, 建议范围: 0.95~1"` } // 偏色检测 type VqdColor struct { - ColorThr float64 `json:"color_thr" comment:"默认为0.18, 取值范围: 0~1, 建议范围: 0.1~0.5"` - ColorAbnNumRatio float64 `json:"color_abn_num_ratio" comment:"默认为0.5, 取值范围: 0~1, 建议范围: 0.3~0.9"` + ColorThr float32 `json:"color_thr" comment:"默认为0.18, 取值范围: 0~1, 建议范围: 0.1~0.5"` + ColorAbnNumRatio float32 `json:"color_abn_num_ratio" comment:"默认为0.5, 取值范围: 0~1, 建议范围: 0.3~0.9"` } // 遮挡检测 type VqdOcclusion struct { - OcclusionThr float64 `json:"occlusion_thr" comment:"默认为0.1, 取值范围: 0~1, 建议范围: 0.05~0.5"` - OcclusionAbnNumRatio float64 `json:"occlusion_abn_num_ratio" comment:"默认为0.5, 取值范围: 0~1, 建议范围: 0.3~0.9"` + OcclusionThr float32 `json:"occlusion_thr" comment:"默认为0.1, 取值范围: 0~1, 建议范围: 0.05~0.5"` + OcclusionAbnNumRatio float32 `json:"occlusion_abn_num_ratio" comment:"默认为0.5, 取值范围: 0~1, 建议范围: 0.3~0.9"` } // 噪声检测 type VqdNoise struct { - NoiseThr float64 `json:"noise_thr" comment:"默认为 0.3, 取值范围: 0~1, 建议范围: 0.2~0.8"` - NoiseAbnNumRatio float64 `json:"noise_abn_num_ratio" comment:"默认为0.6, 取值范围: 0~1, 建议范围: 0.3~0.9"` + NoiseThr float32 `json:"noise_thr" comment:"默认为 0.3, 取值范围: 0~1, 建议范围: 0.2~0.8"` + NoiseAbnNumRatio float32 `json:"noise_abn_num_ratio" comment:"默认为0.6, 取值范围: 0~1, 建议范围: 0.3~0.9"` } // 对比度检测 type VqdContrast struct { - CtraLowThr float64 `json:"ctra_low_thr" comment:"默认为 0.2, 取值范围: 0~1, 建议范围: 0.1~0.3"` - CtraHighThr float64 `json:"ctra_high_thr" comment:"默认为 0.8, 取值范围: 0~1, 建议范围: 0.7~0.9"` - CtraAbnNumRatio float64 `json:"ctra_abn_num_ratio" comment:"默认为0.5, 取值范围: 0~1, 建议范围: 0.3~0.9"` + CtraLowThr float32 `json:"ctra_low_thr" comment:"默认为 0.2, 取值范围: 0~1, 建议范围: 0.1~0.3"` + CtraHighThr float32 `json:"ctra_high_thr" comment:"默认为 0.8, 取值范围: 0~1, 建议范围: 0.7~0.9"` + CtraAbnNumRatio float32 `json:"ctra_abn_num_ratio" comment:"默认为0.5, 取值范围: 0~1, 建议范围: 0.3~0.9"` } // 马赛克检测 type VqdMosaic struct { - MosaicThr float64 `json:"mosaic_thr" comment:"默认为 0.1 取值范围: 0~1, 建议范围: 0.1~0.9"` - MosaicAbnNumRatio float64 `json:"mosaic_abn_num_ratio" comment:"默认为0.5,取值范围: 0~1, 建议范围: 0.3"` + MosaicThr float32 `json:"mosaic_thr" comment:"默认为 0.1 取值范围: 0~1, 建议范围: 0.1~0.9"` + MosaicAbnNumRatio float32 `json:"mosaic_abn_num_ratio" comment:"默认为0.5,取值范围: 0~1, 建议范围: 0.3"` } // 花屏检测 type VqdFlower struct { - FlowerThr float64 `json:"flower_thr" comment:"默认为 0.3 取值范围: 0~1, 建议范围: 0.1~0.9"` - FlowerAbnNumRatio float64 `json:"flower_abn_num_ratio" comment:"默认为0.6, 取值范围: 0~1, 建议范围: 0.3"` - MosaicThr float64 `json:"mosaic_thr" comment:"默认为 0.3 取值范围: 0~1, 建议范围: 0.1~0.9"` + FlowerThr float32 `json:"flower_thr" comment:"默认为 0.3 取值范围: 0~1, 建议范围: 0.1~0.9"` + FlowerAbnNumRatio float32 `json:"flower_abn_num_ratio" comment:"默认为0.6, 取值范围: 0~1, 建议范围: 0.3"` + MosaicThr float32 `json:"mosaic_thr" comment:"默认为 0.3 取值范围: 0~1, 建议范围: 0.1~0.9"` } diff --git a/internal/core/host/core.go b/internal/core/host/core.go index 1ec129c..ebabffc 100644 --- a/internal/core/host/core.go +++ b/internal/core/host/core.go @@ -52,8 +52,8 @@ func NewCore(cfg *conf.Bootstrap) *Core { } sdk.AddResponseHandler("stop", core.stop) sdk.AddResponseHandler("ping", core.ping) - // 这部分都是收到响应后的回调 + sdk.AddResponseHandler("play", core.playRespH) sdk.AddResponseHandler("findDevices", core.findDevicesRespH) sdk.AddResponseHandler("findChannels", core.findChannelsRespH) sdk.AddResponseHandler("getBaseConfig", core.getBaseConfigRespH) @@ -102,6 +102,11 @@ func (c Core) findTalkUrlRespH(requestID string, args json.RawMessage) (interfac slog.Debug("Received 'findTalkUrl' from host", "request_id", requestID, "args", args) return nil, nil } +func (c Core) playRespH(requestID string, args json.RawMessage) (interface{}, error) { + slog.Debug("Received 'play' from host", "request_id", requestID, "args", args) + return nil, nil +} + func (c Core) iframeDataRespH(requestID string, args json.RawMessage) (interface{}, error) { slog.Debug("Received 'iframeData' from host", "request_id", requestID, "args", args) return nil, nil diff --git a/internal/core/host/play.go b/internal/core/host/play.go new file mode 100644 index 0000000..1b2c617 --- /dev/null +++ b/internal/core/host/play.go @@ -0,0 +1,24 @@ +package host + +import ( + "context" + "encoding/json" +) + +func (c Core) Play(ctx context.Context, in *PlayInput) (*PlayOutput, error) { + marshal, err := json.Marshal(in) + if err != nil { + return nil, err + } + result, err := c.Plugin.CallHost("play", marshal) + + if err != nil { + return nil, err + } + + out := PlayOutput{} + if err = json.Unmarshal(result, &out); err != nil { + return nil, err + } + return &out, nil +} diff --git a/internal/core/host/play.param.go b/internal/core/host/play.param.go new file mode 100644 index 0000000..19e26d1 --- /dev/null +++ b/internal/core/host/play.param.go @@ -0,0 +1,40 @@ +package host + +type PlayInput struct { + ChannelID string `json:"channel_id"` + Stream string `json:"stream"` // 主子码流 MAIN/SUB + Protocol string `json:"protocol"` // hls/webrtc/flv 等播放协议 + Network string `json:"network"` // LAN:内网;WAN:公网(rtsp/rtmp 返回的地址) + ActiveSecond int `json:"active_second"` // 流活跃时间 + TimeS int `form:"time_s" json:"time_s"` // 秒 + + IsRecord bool `json:"-"` // 是否由录像拉起 + Reason string `json:"-"` // 调用原因 + Domain string `json:"-"` // 域名 + RequestHost string `json:"-"` // 请求 url 上的 host,用于自适应返回播放地址 + Host string `json:"-"` + + // 在未来的版本中,将废弃 + IsHTTPS bool `json:"-"` // 是否是 https 请求 + Auth bool `json:"-"` // 是否是 auth 请求 +} +type PlayOutput struct { + ChannelID string `json:"channel_id"` + StreamID string `json:"stream_id"` + Address map[string]string `json:"address"` + Routes []Route `json:"routes"` // 多线路 + Img []byte `json:"img"` + ImgType string `json:"img_type"` + ImgCreateAt int64 `json:"img_created_at"` +} +type Route struct { + ID int `json:"id"` + Label string `json:"label"` + + HTTPFLV string `json:"http_flv"` + WSFLV string `json:"ws_flv"` + HLS string `json:"hls"` + RTMP string `json:"rtmp"` + RTSP string `json:"rtsp"` + WebRTC string `json:"webrtc"` +} diff --git a/internal/core/vqd/model.go b/internal/core/vqd/model.go index acc0a75..d4d9df6 100644 --- a/internal/core/vqd/model.go +++ b/internal/core/vqd/model.go @@ -60,9 +60,9 @@ func (i *VqdConfig) Scan(input interface{}) error { // 亮度检测 type VqdLgtDark struct { Enable bool `gorm:"column:enable;notNull;default:FALSE;comment:启用" json:"enable"` // 启用 - DarkThr float64 `gorm:"column:dark_thr;notNull;default:0;comment:过暗阈值" json:"dark_thr"` // 默认 0.4, 取值范围: 0~1, 建议范围: 0.2~0.6 - LgtThr float64 `gorm:"column:lgt_thr;notNull;default:0;comment:过亮阈值" json:"lgt_thr"` // 默认 0.1, 取值范围: 0~1, 建议范围: 0.1~0.5 - LgtDarkAbnNumRatio float64 `gorm:"column:lgt_dark_abn_num_ratio;notNull;default:0;comment:偏暗或者偏亮次数比例" json:"lgt_dark_abn_num_ratio"` // 默认为0.5, 取值范围: 0~1, 建议范围: 0.1~0.9 + DarkThr float32 `gorm:"column:dark_thr;notNull;default:0;comment:过暗阈值" json:"dark_thr"` // 默认 0.4, 取值范围: 0~1, 建议范围: 0.2~0.6 + LgtThr float32 `gorm:"column:lgt_thr;notNull;default:0;comment:过亮阈值" json:"lgt_thr"` // 默认 0.1, 取值范围: 0~1, 建议范围: 0.1~0.5 + LgtDarkAbnNumRatio float32 `gorm:"column:lgt_dark_abn_num_ratio;notNull;default:0;comment:偏暗或者偏亮次数比例" json:"lgt_dark_abn_num_ratio"` // 默认为0.5, 取值范围: 0~1, 建议范围: 0.1~0.9 } func (a VqdLgtDark) Value() (driver.Value, error) { @@ -75,8 +75,8 @@ func (i *VqdLgtDark) Scan(input interface{}) error { // 蓝屏检查 type VqdBlue struct { Enable bool `gorm:"column:enable;notNull;default:FALSE;comment:启用" json:"enable"` // 启用 - BlueThr float64 `gorm:"column:blue_thr;notNull;default:0;comment:蓝屏判断阈值" json:"blue_thr"` // 默认为 0.6, 取值范围: 0~1, 建议范围 0.4~0.9 - BlueAbnNumRatio float64 `gorm:"column:blue_abn_num_ratio;notNull;default:0;comment:蓝屏次数比例" json:"blue_abn_num_ratio"` // 默认为0.5, 取值范围: 0~1, 建议范围: 0.1~0.9 + BlueThr float32 `gorm:"column:blue_thr;notNull;default:0;comment:蓝屏判断阈值" json:"blue_thr"` // 默认为 0.6, 取值范围: 0~1, 建议范围 0.4~0.9 + BlueAbnNumRatio float32 `gorm:"column:blue_abn_num_ratio;notNull;default:0;comment:蓝屏次数比例" json:"blue_abn_num_ratio"` // 默认为0.5, 取值范围: 0~1, 建议范围: 0.1~0.9 } func (a VqdBlue) Value() (driver.Value, error) { @@ -89,8 +89,8 @@ func (i *VqdBlue) Scan(input interface{}) error { // 清晰度检查 type VqdClarity struct { Enable bool `gorm:"column:enable;notNull;default:FALSE;comment:启用" json:"enable"` // 启用 - ClarityThr float64 `gorm:"column:clarity_thr;notNull;default:0;comment:清晰度判断阈值" json:"clarity_thr"` // 默认为0.4, 取值范围: 0~1, 建议范围: 0.3~0.99 - ClarityAbnNumRatio float64 `gorm:"column:clarity_abn_num_ratio;notNull;default:0;comment:清晰度异常次数比例" json:"clarity_abn_num_ratio"` // 默认为0.5, 取值范围: 0~1, 建议范围: 0.1~0.9 + ClarityThr float32 `gorm:"column:clarity_thr;notNull;default:0;comment:清晰度判断阈值" json:"clarity_thr"` // 默认为0.4, 取值范围: 0~1, 建议范围: 0.3~0.99 + ClarityAbnNumRatio float32 `gorm:"column:clarity_abn_num_ratio;notNull;default:0;comment:清晰度异常次数比例" json:"clarity_abn_num_ratio"` // 默认为0.5, 取值范围: 0~1, 建议范围: 0.1~0.9 } func (a VqdClarity) Value() (driver.Value, error) { @@ -103,8 +103,8 @@ func (i *VqdClarity) Scan(input interface{}) error { // 抖动检查 type VqdShark struct { Enable bool `gorm:"column:enable;notNull;default:FALSE;comment:启用" json:"enable"` // 启用 - SharkThr float64 `gorm:"column:shark_thr;notNull;default:0;comment:抖动阈值参数" json:"shark_thr"` // 默认为 0.2, 取值范围: 0~1, 建议范围: 0.1~0.8 - SharkAbnNumRatio float64 `gorm:"column:shark_abn_num_ratio;notNull;default:0;comment:出现抖动次数的比例" json:"shark_abn_num_ratio"` // 默认为0.2, 取值范围: 0~1, 建议范围: 0.1~0.6 + SharkThr float32 `gorm:"column:shark_thr;notNull;default:0;comment:抖动阈值参数" json:"shark_thr"` // 默认为 0.2, 取值范围: 0~1, 建议范围: 0.1~0.8 + SharkAbnNumRatio float32 `gorm:"column:shark_abn_num_ratio;notNull;default:0;comment:出现抖动次数的比例" json:"shark_abn_num_ratio"` // 默认为0.2, 取值范围: 0~1, 建议范围: 0.1~0.6 } func (a VqdShark) Value() (driver.Value, error) { @@ -117,8 +117,8 @@ func (i *VqdShark) Scan(input interface{}) error { // 冻结检测 type VqdFreeze struct { Enable bool `gorm:"column:enable;notNull;default:FALSE;comment:启用" json:"enable"` // 启用 - FreezeThr float64 `gorm:"column:freeze_thr;notNull;default:0;comment:冻结阈值参数" json:"freeze_thr"` // 默认 0.4, 取值范围: 0~1, 建议范围: 0.2~0.6 - FreezeAbnNumRatio float64 `gorm:"column:freeze_abn_num_ratio;notNull;default:0;comment:冻结帧数占得比例" json:"freeze_abn_num_ratio"` // 默认为0.99, 取值范围: 0.8~1, 建议范围: 0.95~1 + FreezeThr float32 `gorm:"column:freeze_thr;notNull;default:0;comment:冻结阈值参数" json:"freeze_thr"` // 默认 0.4, 取值范围: 0~1, 建议范围: 0.2~0.6 + FreezeAbnNumRatio float32 `gorm:"column:freeze_abn_num_ratio;notNull;default:0;comment:冻结帧数占得比例" json:"freeze_abn_num_ratio"` // 默认为0.99, 取值范围: 0.8~1, 建议范围: 0.95~1 } func (a VqdFreeze) Value() (driver.Value, error) { @@ -131,8 +131,8 @@ func (i *VqdFreeze) Scan(input interface{}) error { // 偏色检测 type VqdColor struct { Enable bool `gorm:"column:enable;notNull;default:FALSE;comment:启用" json:"enable"` // 启用 - ColorThr float64 `gorm:"column:color_thr;notNull;default:0;comment:偏色判断值" json:"color_thr"` // 默认为0.18, 取值范围: 0~1, 建议范围: 0.1~0.5 - ColorAbnNumRatio float64 `gorm:"column:color_abn_num_ratio;notNull;default:0;comment:偏色次数比例" json:"color_abn_num_ratio"` // 默认为0.5, 取值范围: 0~1, 建议范围: 0.3~0.9 + ColorThr float32 `gorm:"column:color_thr;notNull;default:0;comment:偏色判断值" json:"color_thr"` // 默认为0.18, 取值范围: 0~1, 建议范围: 0.1~0.5 + ColorAbnNumRatio float32 `gorm:"column:color_abn_num_ratio;notNull;default:0;comment:偏色次数比例" json:"color_abn_num_ratio"` // 默认为0.5, 取值范围: 0~1, 建议范围: 0.3~0.9 } func (a VqdColor) Value() (driver.Value, error) { @@ -145,8 +145,8 @@ func (i *VqdColor) Scan(input interface{}) error { // 遮挡检测 type VqdOcclusion struct { Enable bool `gorm:"column:enable;notNull;default:FALSE;comment:启用" json:"enable"` // 启用 - OcclusionThr float64 `gorm:"column:occlusion_thr;notNull;default:0;comment:遮挡判断阈值" json:"occlusion_thr"` // 默认为0.1, 取值范围: 0~1, 建议范围: 0.05~0.5 - OcclusionAbnNumRatio float64 `gorm:"column:occlusion_abn_num_ratio;notNull;default:0;comment:遮挡次数比例" json:"occlusion_abn_num_ratio"` // 默认为0.5, 取值范围: 0~1, 建议范围: 0.3~0.9 + OcclusionThr float32 `gorm:"column:occlusion_thr;notNull;default:0;comment:遮挡判断阈值" json:"occlusion_thr"` // 默认为0.1, 取值范围: 0~1, 建议范围: 0.05~0.5 + OcclusionAbnNumRatio float32 `gorm:"column:occlusion_abn_num_ratio;notNull;default:0;comment:遮挡次数比例" json:"occlusion_abn_num_ratio"` // 默认为0.5, 取值范围: 0~1, 建议范围: 0.3~0.9 } func (a VqdOcclusion) Value() (driver.Value, error) { @@ -159,8 +159,8 @@ func (i *VqdOcclusion) Scan(input interface{}) error { // 噪声检测 type VqdNoise struct { Enable bool `gorm:"column:enable;notNull;default:FALSE;comment:启用" json:"enable"` // 启用 - NoiseThr float64 `gorm:"column:noise_thr;notNull;default:0;comment:噪声判断阈值" json:"noise_thr"` // 默认为 0.3, 取值范围: 0~1, 建议范围: 0.2~0.8 - NoiseAbnNumRatio float64 `gorm:"column:noise_abn_num_ratio;notNull;default:0;comment:噪声次数比例" json:"noise_abn_num_ratio"` // 默认为0.6, 取值范围: 0~1, 建议范围: 0.3~0.9 + NoiseThr float32 `gorm:"column:noise_thr;notNull;default:0;comment:噪声判断阈值" json:"noise_thr"` // 默认为 0.3, 取值范围: 0~1, 建议范围: 0.2~0.8 + NoiseAbnNumRatio float32 `gorm:"column:noise_abn_num_ratio;notNull;default:0;comment:噪声次数比例" json:"noise_abn_num_ratio"` // 默认为0.6, 取值范围: 0~1, 建议范围: 0.3~0.9 } func (a VqdNoise) Value() (driver.Value, error) { @@ -173,9 +173,9 @@ func (i *VqdNoise) Scan(input interface{}) error { // 对比度检测 type VqdContrast struct { Enable bool `gorm:"column:enable;notNull;default:FALSE;comment:启用" json:"enable"` // 启用 - CtraLowThr float64 `gorm:"column:ctra_low_thr;notNull;default:0;comment:低对比度判断阈值" json:"ctra_low_thr"` // 默认为 0.2, 取值范围: 0~1, 建议范围: 0.1~0.3 - CtraHighThr float64 `gorm:"column:ctra_high_thr;notNull;default:0;comment:高对比度判断阈值" json:"ctra_high_thr"` // 默认为 0.8, 取值范围: 0~1, 建议范围: 0.7~0.9 - CtraAbnNumRatio float64 `gorm:"column:ctra_abn_num_ratio;notNull;default:0;comment:对比度异常次数比例" json:"ctra_abn_num_ratio"` // 默认为0.5, 取值范围: 0~1, 建议范围: 0.3~0.9 + CtraLowThr float32 `gorm:"column:ctra_low_thr;notNull;default:0;comment:低对比度判断阈值" json:"ctra_low_thr"` // 默认为 0.2, 取值范围: 0~1, 建议范围: 0.1~0.3 + CtraHighThr float32 `gorm:"column:ctra_high_thr;notNull;default:0;comment:高对比度判断阈值" json:"ctra_high_thr"` // 默认为 0.8, 取值范围: 0~1, 建议范围: 0.7~0.9 + CtraAbnNumRatio float32 `gorm:"column:ctra_abn_num_ratio;notNull;default:0;comment:对比度异常次数比例" json:"ctra_abn_num_ratio"` // 默认为0.5, 取值范围: 0~1, 建议范围: 0.3~0.9 } func (a VqdContrast) Value() (driver.Value, error) { @@ -188,8 +188,8 @@ func (i *VqdContrast) Scan(input interface{}) error { // 马赛克检测 type VqdMosaic struct { Enable bool `gorm:"column:enable;notNull;default:FALSE;comment:启用" json:"enable"` // 启用 - MosaicThr float64 `gorm:"column:mosaic_thr;notNull;default:0;comment:马赛克阈值参数" json:"mosaic_thr"` // 默认为 0.1 取值范围: 0~1, 建议范围: 0.1~0.9 - MosaicAbnNumRatio float64 `gorm:"column:mosaic_abn_num_ratio;notNull;default:0;comment:马赛克次数比例" json:"mosaic_abn_num_ratio"` // 默认为0.5,取值范围: 0~1, 建议范围: 0.3 + MosaicThr float32 `gorm:"column:mosaic_thr;notNull;default:0;comment:马赛克阈值参数" json:"mosaic_thr"` // 默认为 0.1 取值范围: 0~1, 建议范围: 0.1~0.9 + MosaicAbnNumRatio float32 `gorm:"column:mosaic_abn_num_ratio;notNull;default:0;comment:马赛克次数比例" json:"mosaic_abn_num_ratio"` // 默认为0.5,取值范围: 0~1, 建议范围: 0.3 } func (a VqdMosaic) Value() (driver.Value, error) { @@ -202,9 +202,9 @@ func (i *VqdMosaic) Scan(input interface{}) error { // 花屏检测 type VqdFlower struct { Enable bool `gorm:"column:enable;notNull;default:FALSE;comment:启用" json:"enable"` // 启用 - FlowerThr float64 `gorm:"column:flower_thr;notNull;default:0;comment:花屏阈值参数" json:"flower_thr"` // 默认为 0.3 取值范围: 0~1, 建议范围: 0.1~0.9 - FlowerAbnNumRatio float64 `gorm:"column:flower_abn_num_ratio;notNull;default:0;comment:花屏次数比例" json:"flower_abn_num_ratio"` // 默认为0.6, 取值范围: 0~1, 建议范围: 0.3 - MosaicThr float64 `gorm:"column:mosaic_thr;notNull;default:0;comment:阈值" json:"mosaic_thr"` // 默认为 0.3 取值范围: 0~1, 建议范围: 0.1~0.9 + FlowerThr float32 `gorm:"column:flower_thr;notNull;default:0;comment:花屏阈值参数" json:"flower_thr"` // 默认为 0.3 取值范围: 0~1, 建议范围: 0.1~0.9 + FlowerAbnNumRatio float32 `gorm:"column:flower_abn_num_ratio;notNull;default:0;comment:花屏次数比例" json:"flower_abn_num_ratio"` // 默认为0.6, 取值范围: 0~1, 建议范围: 0.3 + MosaicThr float32 `gorm:"column:mosaic_thr;notNull;default:0;comment:阈值" json:"mosaic_thr"` // 默认为 0.3 取值范围: 0~1, 建议范围: 0.1~0.9 } func (a VqdFlower) Value() (driver.Value, error) { @@ -252,18 +252,50 @@ func (*VqdTimeTemplate) TableName() string { return "vqd_time_template" } +type Abnormal struct { + Value float32 `json:"value"` + Name string `json:"name"` +} + +type Abnormals []Abnormal + +func (a Abnormals) Value() (driver.Value, error) { + return json.Marshal(a) +} +func (i *Abnormals) Scan(input interface{}) error { + return orm.JsonUnmarshal(input, i) +} + +type DefaultValue struct { + Thr1 float32 `json:"thr1"` + Name1 string `json:"name1"` + Thr2 float32 `json:"thr2"` + Name2 string `json:"name2"` + Ratio float32 `json:"ratio"` //比例 +} +type DefaultValues []DefaultValue + +func (a DefaultValues) Value() (driver.Value, error) { + return json.Marshal(a) +} +func (i *DefaultValues) Scan(input interface{}) error { + return orm.JsonUnmarshal(input, i) +} + type VqdAlarm struct { orm.Model - AlarmName string `gorm:"column:alarm_name;notNull;default:'';comment:告警名称" json:"alarm_name"` // 告警名称 - AlarmValue string `gorm:"column:alarm_value;notNull;default:'';comment:告警参数" json:"alarm_value"` // 告警参数 - ChannelID string `gorm:"column:channel_id;notNull;default:'';comment:关联通道" json:"channel_id"` // 关联通道 - ChannelName string `gorm:"column:channel_name;notNull;default:'';comment:关联通道名称" json:"channel_name"` // 关联通道名称 - TaskTemplateID int64 `gorm:"column:task_template_id;notNull;default:0;comment:关联模板" json:"task_template_id"` //关联模板 - TaskTemplateName string `gorm:"column:task_template_name;notNull;default:0;comment:关联模板名称" json:"task_template_name"` //关联模板名称 - TaskID int64 `gorm:"column:task_id;notNull;default:0;comment:关联任务" json:"task_id"` // 关联任务名称 - TaskName string `gorm:"column:task_name;notNull;default:'';comment:关联任务名称" json:"task_name"` // 任务名称 - FilePath string `gorm:"column:file_path;notNull;default:'';comment:文件路径" json:"file_path"` // 文件路径 - + IsDeep bool `gorm:"column:is_deep;notNull;default:FALSE;comment:启用" json:"is_deep"` + ChannelID string `gorm:"column:channel_id;notNull;default:'';comment:关联通道" json:"channel_id"` // 关联通道 + ChannelName string `gorm:"column:channel_name;notNull;default:'';comment:关联通道名称" json:"channel_name"` // 关联通道名称 + PlanID int64 `gorm:"column:plan_id;notNull;default:0;comment:关联计划" json:"plan_id"` // 关联计划 + PlanName string `gorm:"column:plan_name;notNull;default:0;comment:关联计划名称" json:"plan_name"` // 关联计划名称 + TaskTemplateID int64 `gorm:"column:task_template_id;notNull;default:0;comment:关联模板" json:"task_template_id"` // 关联模板 + TaskTemplateName string `gorm:"column:task_template_name;notNull;default:0;comment:关联模板名称" json:"task_template_name"` // 关联模板名称 + TaskID int64 `gorm:"column:task_id;notNull;default:0;comment:关联任务" json:"task_id"` // 关联任务名称 + TaskName string `gorm:"column:task_name;notNull;default:'';comment:关联任务名称" json:"task_name"` // 任务名称 + FilePath string `gorm:"column:file_path;notNull;default:'';comment:文件路径" json:"file_path"` // 文件路径 + Abnormals Abnormals `gorm:"column:abnormals;type:jsonb;notNull;default:'{}';comment:告警异常列表" json:"abnormals"` // 告警异常列表 + DefaultValues DefaultValues `gorm:"column:default_values;type:jsonb;notNull;default:'{}';comment:设置的默认阈值" json:"default_values"` // 设置的默认阈值 } // TableName database table name diff --git a/internal/core/vqd/vqdalarm.go b/internal/core/vqd/vqdalarm.go index be1e516..d8c5fbf 100644 --- a/internal/core/vqd/vqdalarm.go +++ b/internal/core/vqd/vqdalarm.go @@ -34,9 +34,9 @@ func (c Core) FindVqdAlarmAll() ([]*VqdAlarm, int64, error) { // FindVqdAlarm Paginated search func (c Core) FindVqdAlarm(ctx context.Context, in *FindVqdAlarmInput) ([]*VqdAlarm, int64, error) { items := make([]*VqdAlarm, 0) - if in.AlarmName != "" { + if in.Name != "" { query := orm.NewQuery(8). - Where("audio_name like ? OR channel_id like ? OR channel_name like ?", "%"+in.AlarmName+"%", "%"+in.AlarmName+"%", "%"+in.AlarmName+"%").OrderBy("created_at DESC") + Where("channel_name like ? OR channel_id like ? OR channel_name like ? OR plan_name like ? OR task_template_name like ? OR task_name like ? ", "%"+in.Name+"%", "%"+in.Name+"%", "%"+in.Name+"%", "%"+in.Name+"%", "%"+in.Name+"%", "%"+in.Name+"%").OrderBy("created_at DESC") total, err := c.store.VqdAlarm().Find(ctx, &items, in, query.Encode()...) if err != nil { return nil, 0, reason.ErrDB.Withf(`Find err[%s]`, err.Error()) diff --git a/internal/core/vqd/vqdalarm.param.go b/internal/core/vqd/vqdalarm.param.go index 568c3ae..bf313be 100644 --- a/internal/core/vqd/vqdalarm.param.go +++ b/internal/core/vqd/vqdalarm.param.go @@ -7,7 +7,7 @@ import ( type FindVqdAlarmInput struct { web.PagerFilter - AlarmName string `form:"alarm_name"` // 名称 + Name string `form:"name"` // 名称 } type EditVqdAlarmInput struct { @@ -15,15 +15,18 @@ type EditVqdAlarmInput struct { } type AddVqdAlarmInput struct { - AlarmName string `json:"alarm_name"` // 告警名称 - AlarmValue string `json:"alarm_value"` // 告警参数 - ChannelID string `json:"channel_id"` // 关联通道 - ChannelName string `json:"channel_name"` // 关联通道名称 - TaskTemplateID int64 `json:"task_template_id"` // 关联模板 - TaskTemplateName string `json:"task_template_name"` // 关联模板名称 - TaskID int64 `json:"task_id"` // 关联任务名称 - TaskName string `json:"task_name"` // 任务名称 - FilePath string `json:"file_path"` // 文件路径 + PlanID int64 `json:"plan_id"` // 关联计划 + PlanName string `json:"plan_name"` // 关联计划名称 + ChannelID string `json:"channel_id"` // 关联通道 + ChannelName string `json:"channel_name"` // 关联通道名称 + TaskTemplateID int64 `json:"task_template_id"` // 关联模板 + TaskTemplateName string `json:"task_template_name"` // 关联模板名称 + TaskID int64 `json:"task_id"` // 关联任务名称 + TaskName string `json:"task_name"` // 任务名称 + FilePath string `json:"file_path"` // 文件路径 + IsDeep bool `json:"is_deep"` + Abnormals Abnormals `json:"abnormals"` // 告警异常列表 + DefaultValues DefaultValues `json:"default_values"` // 设置的默认阈值 } type DelVqdAlarmInput struct { IDs []int `json:"ids"` // id diff --git a/internal/core/vqd/vqdtask.go b/internal/core/vqd/vqdtask.go index c0bffa2..3dfdce8 100644 --- a/internal/core/vqd/vqdtask.go +++ b/internal/core/vqd/vqdtask.go @@ -31,6 +31,34 @@ func (c Core) FindVqdTaskAll() ([]*VqdTask, int64, error) { return items, total, nil } +// FindVqdTemplateID Paginated search +func (c Core) FindVqdPlanID(ctx context.Context, id int) ([]*VqdTask, int64, error) { + items := make([]*VqdTask, 0) + in := &FindVqdTaskInput{} + in.Size = 99999 + in.Page = 1 + query := orm.NewQuery(2).Where("time_template_id = ?", id) + total, err := c.store.VqdTask().Find(ctx, &items, in, query.Encode()...) + if err != nil { + return nil, 0, reason.ErrDB.Withf(`Find err[%s]`, err.Error()) + } + return items, total, nil +} + +// FindVqdTemplateID Paginated search +func (c Core) FindVqdTemplateID(ctx context.Context, id int) ([]*VqdTask, int64, error) { + items := make([]*VqdTask, 0) + in := &FindVqdTaskInput{} + in.Size = 99999 + in.Page = 1 + query := orm.NewQuery(2).Where("task_template_id = ?", id) + total, err := c.store.VqdTask().Find(ctx, &items, in, query.Encode()...) + if err != nil { + return nil, 0, reason.ErrDB.Withf(`Find err[%s]`, err.Error()) + } + return items, total, nil +} + // FindVqdTask Paginated search func (c Core) FindVqdTask(ctx context.Context, in *FindVqdTaskInput) ([]*VqdTask, int64, error) { items := make([]*VqdTask, 0) @@ -64,6 +92,18 @@ func (c Core) FindVqdTaskTemplateID(ctx context.Context, id int) (*VqdTask, erro return &out, nil } +// FindVqdTaskPlanID Query a single object +func (c Core) FindVqdTaskPlanID(ctx context.Context, id int) (*VqdTask, error) { + var out VqdTask + if err := c.store.VqdTask().Get(ctx, &out, orm.Where("time_template_id=?", id)); err != nil { + if orm.IsErrRecordNotFound(err) { + return nil, reason.ErrNotFound.Withf(`Get err[%s]`, err.Error()) + } + return nil, reason.ErrDB.Withf(`Get err[%s]`, err.Error()) + } + return &out, nil +} + // GetVqdTask Query a single object func (c Core) GetVqdTask(ctx context.Context, id int) (*VqdTask, error) { var out VqdTask @@ -86,6 +126,16 @@ func (c Core) GetNameVqdTask(ctx context.Context, name string) (*VqdTask, error) } return &out, nil } +func (c Core) GetNameVqdTaskChannelID(ctx context.Context, name string) (*VqdTask, error) { + var out VqdTask + if err := c.store.VqdTask().Get(ctx, &out, orm.Where("channel_id=?", name)); err != nil { + if orm.IsErrRecordNotFound(err) { + return nil, reason.ErrNotFound.Withf(`Get err[%s]`, err.Error()) + } + return nil, reason.ErrDB.Withf(`Get err[%s]`, err.Error()) + } + return &out, nil +} // AddVqdTask Insert into database func (c Core) AddVqdTask(ctx context.Context, in *AddVqdTaskInput) (*VqdTask, error) { diff --git a/internal/core/vqdtask/clean.go b/internal/core/vqdtask/clean.go index 7b37b08..73ef68c 100644 --- a/internal/core/vqdtask/clean.go +++ b/internal/core/vqdtask/clean.go @@ -1,6 +1,7 @@ package vqdtask import ( + "easyvqd/pkg/vqdcms" "fmt" "io/fs" "log/slog" @@ -12,7 +13,6 @@ import ( // 配置参数 const ( // 要清理的目标目录(请替换为你实际的目录路径) - cleanDir = "/snap" // 定时任务执行间隔(每天执行一次) interval = 24 * time.Hour // 批量删除大小(避免单次删除过多锁表) @@ -195,10 +195,10 @@ func (c Core) cleanExpiredFiles() error { expireDays = 1 } expireTime := time.Now().Add(-expireDays * 24 * time.Hour) - slog.Info(fmt.Sprintf("开始清理目录 [%s] 中超过 %d 天的文件,过期时间阈值:%s", cleanDir, expireDays, expireTime.Format(time.RFC3339))) + slog.Info(fmt.Sprintf("开始清理目录 [%s] 中超过 %d 天的文件,过期时间阈值:%s", vqdcms.VQD_IMAGES_DIR, expireDays, expireTime.Format(time.RFC3339))) dir, _ := os.Getwd() - rootDir := filepath.Join(dir, cleanDir) + rootDir := filepath.Join(dir, vqdcms.VQD_IMAGES_DIR) dateDirs, err := os.ReadDir(rootDir) if err != nil { slog.Error("访问根目录路径失败", "path", rootDir, "err", err) diff --git a/internal/core/vqdtask/core.go b/internal/core/vqdtask/core.go index 40b7c7e..acabccd 100644 --- a/internal/core/vqdtask/core.go +++ b/internal/core/vqdtask/core.go @@ -2,6 +2,7 @@ package vqdtask import ( "bufio" + "context" "easyvqd/internal/conf" "easyvqd/internal/core/host" "easyvqd/internal/core/vqd" @@ -19,6 +20,7 @@ type Core struct { HostCore *host.Core VqdTaskCore *vqd.Core Cfg *conf.Bootstrap + ResultCb vqdcms.VQDResultCB } var ( @@ -31,142 +33,173 @@ func NewCore(HostCore *host.Core, VqdTaskCore *vqd.Core, Cfg *conf.Bootstrap) *C VqdTaskCore: VqdTaskCore, Cfg: Cfg, } - core.HostCore.CbIFrame = func(s string, data []byte, codes int) { - //fmt.Println("res", s, codes, len(data)) - v, ok := VqdTaskMap.LoadTaskMap(s) - if ok { - v.SendData(data, codes) + core.ResultCb = func(v vqdcms.AbnormalModel) { + + in := &vqd.AddVqdAlarmInput{ + ChannelName: v.ChannelName, + TaskTemplateName: v.TemplateName, + TaskName: v.TaskName, + PlanName: v.PlanName, + TaskID: int64(v.ID), + TaskTemplateID: int64(v.TemplateID), + PlanID: int64(v.PlanID), + IsDeep: v.IsDeep, + FilePath: v.FilePath, + } + var Abnormals vqd.Abnormals + if len(v.Abnormals) > 0 { + for _, abnormal := range v.Abnormals { + item := vqd.Abnormal{ + Value: abnormal.Value, + Name: abnormal.Name, + } + Abnormals = append(Abnormals, item) + } + } + in.Abnormals = Abnormals + + var DefaultValues vqd.DefaultValues + if len(v.DefaultValues) > 0 { + for _, defaultValue := range v.DefaultValues { + item := vqd.DefaultValue{ + Thr1: defaultValue.Thr1, + Name1: defaultValue.Name1, + Thr2: defaultValue.Thr2, + Name2: defaultValue.Name2, + Ratio: defaultValue.Ratio, + } + DefaultValues = append(DefaultValues, item) + } + } + in.DefaultValues = DefaultValues + _, err := core.VqdTaskCore.AddVqdAlarm(context.TODO(), in) + if err != nil { + slog.Error("add alarm", "err", err.Error()) } } - + core.HostCore.CbIFrame = func(s string, data []byte, codes int) { + v, ok := VqdTaskMap.LoadTaskMap(s) + if ok { + v.SendData(data, VIDEO_CODEC_H265) + } + //slog.Debug("cb IFrame", "name", s, "codes", codes) + } time.AfterFunc(time.Duration(5)*time.Second, func() { - //in := &vqd.AddVqdAlarmInput{ - // AlarmName: "遮挡告警", - // AlarmValue: "", - // ChannelID: "", - // ChannelName: "", - // TaskTemplateID: 0, - // TaskTemplateName: "", - // TaskID: 0, - // TaskName: "", - // FilePath: "", - //} - //for i := 0; i < 2; i++ { - // core.VqdTaskCore.AddVqdAlarm(context.TODO(), in) - //} - // 启用诊断分析 core.InitVqdTask() - core.AddTaskVqd(1, "PVWPQBPIv32UI_01") }) // 启用定时清理任务 go core.scheduleCleanTask() - // 测试 - //go core.OpenStartVqd() + // 启用任务管理器 return core } -func (c Core) InitVqdTask() { +func (c *Core) InitVqdTask() { err := vqdcms.VQDInit() if err != nil { slog.Error("vqd cms open", "err", err.Error()) return } + all, _, err := c.VqdTaskCore.FindVqdTaskAll() + if err == nil { + for _, vqdTask := range all { + c.AddTaskVqd(vqdTask.ID) + time.Sleep(200 * time.Millisecond) + } + } + return } -func (c Core) UnVqdTask() { +func (c *Core) UnVqdTask() { + VqdTaskMap.DeleteTaskMapAll() vqdcms.VQDUnInit() return } -func (c Core) AddTaskVqd(taskId int, chnId string) { - cb := func(res vqdcms.AbnormalModel) { - fmt.Println("res", res) - } - para := vqdcms.NewVQDPara() - v := vqdcms.NewVQDHandle(cb, taskId, chnId).Create(para) - VqdTaskMap.StoreChildMap(fmt.Sprintf("%s", chnId), v) -} +func (c *Core) AddTaskVqd(taskId int) error { -//func (c Core) OpenStartVqd() { -// -// err := vqdcms.VQDInit() -// if err != nil { -// fmt.Println("程序异常", err.Error()) -// return -// } -// dir, _ := os.Getwd() -// rootPath := filepath.Join(dir, "gbs_buf264") // 你的H.264裸流文件路径 -// -// v := vqdcms.NewVQDHandle(nil, 1) -// -// para := vqdcms.NewVQDPara() -// v.SetVQDConfig(para) -// v.Create(para) -// entries, err := os.ReadDir(rootPath) -// if err != nil { -// fmt.Printf("读取目录失败: %v\n", err) -// return -// } -// -// fmt.Printf("目录 %s 下的内容:\n", dir) -// for _, entry := range entries { -// if entry.IsDir() { -// fmt.Printf("[目录] %s\n", entry.Name()) -// } else { -// h264Paths := filepath.Join(rootPath, entry.Name()) // 你的H.264裸流文件路径 -// fmt.Println(h264Paths) -// data, err := os.ReadFile(h264Paths) -// if err == nil { -// datap := GetIFramePointer(data) -// width, height, buf, err := v.de.PushDataEx(uintptr(datap.Pointer), datap.Length, VIDEO_CODEC_H264) -// if err == nil { -// v.SendData(buf, width, height) -// slog.Info("I帧转YUV成功: ", "h264Paths", h264Paths) -// } else { -// //slog.Error("I帧转YUV失败: ", "h264Paths", h264Paths ) -// } -// } -// } -// } -// -// return -//} -// -//// H264IFrameData 封装I帧数据和指针信息 -//type H264IFrameData struct { -// Data []byte // I帧原始字节数据 -// Pointer unsafe.Pointer // 指向数据的原始指针 -// Length int // 数据长度(字节数) -// IsValid bool // 指针是否有效 -//} -// -//// GetIFramePointer 将字节切片转换为原始指针 -//// 注意:unsafe包的使用会绕过Go的内存安全检查,需谨慎 -//func GetIFramePointer(data []byte) *H264IFrameData { -// if len(data) == 0 { -// return &H264IFrameData{ -// IsValid: false, -// Length: 0, -// } -// } -// -// // 方式1:直接通过unsafe获取切片底层数组的指针(推荐,高效) -// // 切片的底层结构是:指向数组的指针 + 长度 + 容量 -// ptr := unsafe.Pointer(&data[0]) -// -// // 方式2:通过reflect获取指针(更直观展示切片结构,可选) -// // sliceHeader := (*reflect.SliceHeader)(unsafe.Pointer(&data)) -// // ptr := unsafe.Pointer(sliceHeader.Data) -// -// return &H264IFrameData{ -// Data: data, -// Pointer: ptr, -// Length: len(data), -// IsValid: true, -// } -//} + task, err := c.VqdTaskCore.GetVqdTask(context.TODO(), taskId) + if err != nil { + slog.Error("vqd add task find", "err", err.Error()) + return err + } + taskTemplate, err := c.VqdTaskCore.GetIDVqdTaskTemplate(context.TODO(), task.TaskTemplateID) + if err != nil { + slog.Error("vqd add task find template", "err", err.Error()) + return err + } + taskPlan, err := c.VqdTaskCore.GetVqdTimeTemplate(context.TODO(), int(task.TimeTemplateID)) + if err != nil { + slog.Error("vqd add task find plan", "err", err.Error()) + return err + } + chnId := task.ChannelID + + para := vqdcms.NewVQDPara(taskTemplate) + info := vqdcms.VQDHandleInfo{ + ChannelID: chnId, + ChannelName: task.ChannelName, + TaskID: task.ID, + TaskName: task.Name, + TemplateID: taskTemplate.ID, + TemplateName: taskTemplate.Name, + PlanID: taskPlan.ID, + PlanName: taskPlan.Name, + Plans: taskPlan.Plans, + } + v := vqdcms.NewVQDHandle(c.ResultCb, c.HostCore, info).Create(para, taskPlan.Plans) + + VqdTaskMap.StoreChildMap(fmt.Sprintf("%s", chnId), v) + return nil +} +func (c *Core) UpdateTaskVqd(taskId int) error { + task, err := c.VqdTaskCore.GetVqdTask(context.TODO(), taskId) + if err != nil { + slog.Error("vqd update task find", "err", err.Error()) + return err + } + v, ok := VqdTaskMap.LoadTaskMap(task.ChannelID) + if ok { + taskTemplate, err := c.VqdTaskCore.GetIDVqdTaskTemplate(context.TODO(), task.TaskTemplateID) + if err != nil { + slog.Error("vqd update task find template", "err", err.Error()) + return err + } + taskPlan, err := c.VqdTaskCore.GetVqdTimeTemplate(context.TODO(), int(task.TimeTemplateID)) + if err != nil { + slog.Error("vqd add task find plan", "err", err.Error()) + return err + } + para := vqdcms.NewVQDPara(taskTemplate) + info := vqdcms.VQDHandleInfo{ + ChannelID: task.ChannelID, + ChannelName: task.ChannelName, + TaskID: task.ID, + TaskName: task.Name, + TemplateID: taskTemplate.ID, + TemplateName: taskTemplate.Name, + PlanID: taskPlan.ID, + PlanName: taskPlan.Name, + Plans: taskPlan.Plans, + } + errs := v.SetVQDConfig(para, info) + if errs != nil { + slog.Error("vqd update set config err", "err", errs.Error()) + return err + } + VqdTaskMap.StoreChildMap(fmt.Sprintf("%s", task.ChannelID), v) + } + return nil + +} +func (c *Core) DelTaskVqd(taskId int, chnId string) { + v, ok := VqdTaskMap.LoadTaskMap(chnId) + if ok { + v.Destroy() + } + VqdTaskMap.DeleteTaskMap(chnId) +} // 测试i帧数据是否可以转为图片文件 func CheckIFramesToJpg() { diff --git a/internal/core/vqdtask/mode.go b/internal/core/vqdtask/mode.go index 8888525..8b7c33a 100644 --- a/internal/core/vqdtask/mode.go +++ b/internal/core/vqdtask/mode.go @@ -4,3 +4,5 @@ const ( VIDEO_CODEC_H264 = 0x1C VIDEO_CODEC_H265 = 0xAE ) + +//status diff --git a/internal/web/api/api.go b/internal/web/api/api.go index 089bd47..b6b1188 100644 --- a/internal/web/api/api.go +++ b/internal/web/api/api.go @@ -56,8 +56,8 @@ func setupRouter(r *gin.Engine, uc *Usecase) { r.GET("/app/metrics/api", web.WrapH(uc.getMetricsAPI)) //快照 dir, _ := os.Getwd() - uploadsDir := filepath.Join(dir, "uploads") - r.Use(statics.Serve("/uploads", statics.LocalFile(uploadsDir, true))) + uploadsDir := filepath.Join(dir, "snap") + r.Use(statics.Serve("/snap", statics.LocalFile(uploadsDir, true))) versionapi.Register(r, uc.Version, auth) registerConfig(r, ConfigAPI{uc: uc, cfg: uc.Conf}) @@ -77,9 +77,9 @@ func setupRouter(r *gin.Engine, uc *Usecase) { ctx.Redirect(http.StatusTemporaryRedirect, target) return } - if strings.HasPrefix(p, "/uploads/") { + if strings.HasPrefix(p, "/snap/") { q := ctx.Request.URL.RawQuery - target := "/uploads/" + target := "/snap/" if q != "" { target = target + "?" + q } diff --git a/internal/web/api/vqdalarm.go b/internal/web/api/vqdalarm.go index 1e14c9a..c12f19d 100644 --- a/internal/web/api/vqdalarm.go +++ b/internal/web/api/vqdalarm.go @@ -16,15 +16,18 @@ func (a VqdTaskAPI) findVqdAlarm(c *gin.Context, in *vqd.FindVqdAlarmInput) (any //row := structs.Map(item) row := make(map[string]interface{}) row["id"] = item.ID - row["alarm_name"] = item.AlarmName - row["alarm_value"] = item.AlarmValue + row["is_deep"] = item.IsDeep row["channel_id"] = item.ChannelID row["channel_name"] = item.ChannelName + row["plan_id"] = item.PlanID + row["plan_name"] = item.PlanName row["task_template_id"] = item.TaskTemplateID row["task_template_name"] = item.TaskTemplateName row["task_id"] = item.TaskID row["task_name"] = item.TaskName row["file_path"] = item.FilePath + row["abnormals"] = item.Abnormals + row["default_values"] = item.DefaultValues row["created_at"] = item.CreatedAt row["updated_at"] = item.UpdatedAt @@ -42,15 +45,18 @@ func (a VqdTaskAPI) getVqdAlarm(c *gin.Context, _ *struct{}) (any, error) { row := make(map[string]interface{}) row["id"] = item.ID - row["alarm_name"] = item.AlarmName - row["alarm_value"] = item.AlarmValue + row["is_deep"] = item.IsDeep row["channel_id"] = item.ChannelID row["channel_name"] = item.ChannelName + row["plan_id"] = item.PlanID + row["plan_name"] = item.PlanName row["task_template_id"] = item.TaskTemplateID row["task_template_name"] = item.TaskTemplateName row["task_id"] = item.TaskID row["task_name"] = item.TaskName row["file_path"] = item.FilePath + row["abnormals"] = item.Abnormals + row["default_values"] = item.DefaultValues row["created_at"] = item.CreatedAt row["updated_at"] = item.UpdatedAt diff --git a/internal/web/api/vqdtask.go b/internal/web/api/vqdtask.go index f6a7031..c6236c7 100644 --- a/internal/web/api/vqdtask.go +++ b/internal/web/api/vqdtask.go @@ -82,6 +82,13 @@ func (a VqdTaskAPI) findVqdTask(c *gin.Context, in *vqd.FindVqdTaskInput) (any, if errs == nil && template != nil { row["time_template_name"] = timeTemplate.Name } + row["error_msg"] = "" + row["status"] = 0 + v, ok := vqdtask.VqdTaskMap.LoadTaskMap(item.ChannelID) + if ok { + row["error_msg"] = v.ErrorMsg + row["status"] = v.Status + } row["created_at"] = item.CreatedAt row["updated_at"] = item.UpdatedAt @@ -115,6 +122,13 @@ func (a VqdTaskAPI) getVqdTask(c *gin.Context, _ *struct{}) (any, error) { if errs == nil && template != nil { row["time_template_name"] = timeTemplate.Name } + row["error_msg"] = "" + row["status"] = 0 + v, ok := vqdtask.VqdTaskMap.LoadTaskMap(item.ChannelID) + if ok { + row["error_msg"] = v.ErrorMsg + row["status"] = v.Status + } row["created_at"] = item.CreatedAt row["updated_at"] = item.UpdatedAt @@ -122,34 +136,62 @@ func (a VqdTaskAPI) getVqdTask(c *gin.Context, _ *struct{}) (any, error) { return gin.H{"data": row}, err } func (a VqdTaskAPI) addVqdTask(c *gin.Context, in *vqd.AddVqdTaskInput) (any, error) { - _, err := a.core.AddVqdTask(c.Request.Context(), in) + taskChn, err := a.core.GetNameVqdTaskChannelID(c.Request.Context(), in.ChannelID) + if err == nil && taskChn != nil { + return nil, reason.ErrServer.SetMsg(fmt.Sprintf(`通道任务已存在 [%s] `, taskChn.ChannelID)) + } + + task, err := a.core.AddVqdTask(c.Request.Context(), in) if err != nil { return nil, reason.ErrServer.SetMsg(fmt.Sprintf(`add vqdcms err [%s]`, err.Error())) } - + err = a.vqdSdkCore.AddTaskVqd(task.ID) + if err != nil { + return nil, reason.ErrServer.SetMsg(fmt.Sprintf(`add task err [%s]`, err.Error())) + } return gin.H{"data": "OK!"}, err } func (a VqdTaskAPI) editVqdTask(c *gin.Context, in *vqd.EditVqdTaskInput) (any, error) { ID, _ := strconv.Atoi(c.Param("id")) - _, err := a.core.GetVqdTask(c.Request.Context(), ID) + task, err := a.core.GetVqdTask(c.Request.Context(), ID) if err != nil { return nil, reason.ErrServer.SetMsg(fmt.Sprintf(`find vqdcms [%d] err [%s]`, ID, err.Error())) } - - _, err = a.core.EditVqdTask(c.Request.Context(), in, ID) + if task.ChannelID != in.ChannelID { + taskChn, errs := a.core.GetNameVqdTaskChannelID(c.Request.Context(), in.ChannelID) + if errs == nil && taskChn != nil { + return nil, reason.ErrServer.SetMsg(fmt.Sprintf(`通道任务已存在 [%s] `, taskChn.ChannelID)) + } + } + info, err := a.core.EditVqdTask(c.Request.Context(), in, ID) if err != nil { return nil, reason.ErrServer.SetMsg(fmt.Sprintf(`edit vqdcms err [%s]`, err.Error())) } + if task.Enable == false && in.Enable { + err = a.vqdSdkCore.AddTaskVqd(task.ID) + if err != nil { + return nil, reason.ErrServer.SetMsg(fmt.Sprintf(`add task err [%s]`, err.Error())) + } + } else { + if info.Enable { + err = a.vqdSdkCore.UpdateTaskVqd(ID) + if err != nil { + return nil, reason.ErrServer.SetMsg(fmt.Sprintf(`update task err [%s]`, err.Error())) + } + } else { + a.vqdSdkCore.DelTaskVqd(ID, info.ChannelID) + } + } return gin.H{"data": "OK!"}, err } func (a VqdTaskAPI) delVqdTask(c *gin.Context, _ *struct{}) (any, error) { ID, _ := strconv.Atoi(c.Param("id")) - _, err := a.core.DelVqdTask(c.Request.Context(), ID) + task, err := a.core.DelVqdTask(c.Request.Context(), ID) if err != nil { return nil, reason.ErrServer.SetMsg(fmt.Sprintf(`del vqdcms [%d] err [%s]`, ID, err.Error())) } - + a.vqdSdkCore.DelTaskVqd(ID, task.ChannelID) return gin.H{"data": "OK!"}, err } diff --git a/internal/web/api/vqdtasktemplate.go b/internal/web/api/vqdtasktemplate.go index c37a8d0..052d021 100644 --- a/internal/web/api/vqdtasktemplate.go +++ b/internal/web/api/vqdtasktemplate.go @@ -69,17 +69,27 @@ func (a VqdTaskAPI) getVqdTaskTemplate(c *gin.Context, _ *struct{}) (any, error) return gin.H{"data": row}, err } func (a VqdTaskAPI) addVqdTaskTemplate(c *gin.Context, in *vqd.AddVqdTaskTemplateInput) (any, error) { - _, err := a.core.AddVqdTaskTemplate(c.Request.Context(), in) + template, err := a.core.AddVqdTaskTemplate(c.Request.Context(), in) if err != nil { return nil, reason.ErrServer.SetMsg(fmt.Sprintf(`add vqdcms err [%s]`, err.Error())) } + out, _, err := a.core.FindVqdTemplateID(c.Request.Context(), template.ID) + if err != nil { + return nil, reason.ErrServer.SetMsg(fmt.Sprintf(`find template all err [%s]`, err.Error())) + } + for _, task := range out { + err = a.vqdSdkCore.UpdateTaskVqd(task.ID) + if err != nil { + continue + } + } return gin.H{"data": "OK!"}, err } func (a VqdTaskAPI) editVqdTaskTemplate(c *gin.Context, in *vqd.EditVqdTaskTemplateInput) (any, error) { ID, _ := strconv.Atoi(c.Param("id")) - _, err := a.core.GetVqdTaskTemplate(c.Request.Context(), ID) + template, err := a.core.GetVqdTaskTemplate(c.Request.Context(), ID) if err != nil { return nil, reason.ErrServer.SetMsg(fmt.Sprintf(`find vqdcms [%d] err [%s]`, ID, err.Error())) } @@ -88,6 +98,16 @@ func (a VqdTaskAPI) editVqdTaskTemplate(c *gin.Context, in *vqd.EditVqdTaskTempl if err != nil { return nil, reason.ErrServer.SetMsg(fmt.Sprintf(`edit vqdcms err [%s]`, err.Error())) } + out, _, err := a.core.FindVqdTemplateID(c.Request.Context(), template.ID) + if err != nil { + return nil, reason.ErrServer.SetMsg(fmt.Sprintf(`find template all err [%s]`, err.Error())) + } + for _, task := range out { + err = a.vqdSdkCore.UpdateTaskVqd(task.ID) + if err != nil { + continue + } + } return gin.H{"data": "OK!"}, err } diff --git a/internal/web/api/vqdtimetemplate.go b/internal/web/api/vqdtimetemplate.go index eb9a85b..3c4d36f 100644 --- a/internal/web/api/vqdtimetemplate.go +++ b/internal/web/api/vqdtimetemplate.go @@ -49,17 +49,27 @@ func (a VqdTaskAPI) getVqdTimeTemplate(c *gin.Context, _ *struct{}) (any, error) return gin.H{"data": row}, err } func (a VqdTaskAPI) addVqdTimeTemplate(c *gin.Context, in *vqd.AddVqdTimeTemplateInput) (any, error) { - _, err := a.core.AddVqdTimeTemplate(c.Request.Context(), in) + plan, err := a.core.AddVqdTimeTemplate(c.Request.Context(), in) if err != nil { return nil, reason.ErrServer.SetMsg(fmt.Sprintf(`add vqdcms err [%s]`, err.Error())) } + out, _, err := a.core.FindVqdPlanID(c.Request.Context(), plan.ID) + if err != nil { + return nil, reason.ErrServer.SetMsg(fmt.Sprintf(`find plan all err [%s]`, err.Error())) + } + for _, task := range out { + err = a.vqdSdkCore.UpdateTaskVqd(task.ID) + if err != nil { + continue + } + } return gin.H{"data": "OK!"}, err } func (a VqdTaskAPI) editVqdTimeTemplate(c *gin.Context, in *vqd.EditVqdTimeTemplateInput) (any, error) { ID, _ := strconv.Atoi(c.Param("id")) - _, err := a.core.GetVqdTimeTemplate(c.Request.Context(), ID) + plan, err := a.core.GetVqdTimeTemplate(c.Request.Context(), ID) if err != nil { return nil, reason.ErrServer.SetMsg(fmt.Sprintf(`find vqdcms [%d] err [%s]`, ID, err.Error())) } @@ -68,6 +78,16 @@ func (a VqdTaskAPI) editVqdTimeTemplate(c *gin.Context, in *vqd.EditVqdTimeTempl if err != nil { return nil, reason.ErrServer.SetMsg(fmt.Sprintf(`edit vqdcms err [%s]`, err.Error())) } + out, _, err := a.core.FindVqdPlanID(c.Request.Context(), plan.ID) + if err != nil { + return nil, reason.ErrServer.SetMsg(fmt.Sprintf(`find plan all err [%s]`, err.Error())) + } + for _, task := range out { + err = a.vqdSdkCore.UpdateTaskVqd(task.ID) + if err != nil { + continue + } + } return gin.H{"data": "OK!"}, err } @@ -78,14 +98,14 @@ func (a VqdTaskAPI) delVqdTimeTemplate(c *gin.Context, _ *struct{}) (any, error) return nil, reason.ErrServer.SetMsg(fmt.Sprintf(`find vqdcms [%d] err [%s]`, ID, err.Error())) } if info.IsDefault { - return nil, reason.ErrServer.SetMsg(fmt.Sprintf(`默认模板不支持删除 [%s] `, info.Name)) + return nil, reason.ErrServer.SetMsg(fmt.Sprintf(`计划模板不支持删除 [%s] `, info.Name)) + } + planInfo, err := a.core.FindVqdTaskPlanID(c.Request.Context(), ID) + if err == nil { + if planInfo != nil { + return nil, reason.ErrServer.SetMsg(fmt.Sprintf(`计划关联【%s】任务,需先清理【%s】任务后才能删除模板!`, planInfo.Name, planInfo.Name)) + } } - //templateInfo, err := a.core.FindVqdTimeTemplateID(c.Request.Context(), ID) - //if err == nil { - // if templateInfo != nil { - // return nil, reason.ErrServer.SetMsg(fmt.Sprintf(`模板关联【%s】任务,需先清理【%s】任务后才能删除模板!`, templateInfo.Name, templateInfo.Name)) - // } - //} _, err = a.core.DelVqdTimeTemplate(c.Request.Context(), ID) if err != nil { return nil, reason.ErrServer.SetMsg(fmt.Sprintf(`del vqdcms [%d] err [%s]`, ID, err.Error())) diff --git a/pkg/decoder/core.go b/pkg/decoder/core.go index dd03a5b..f8f613c 100644 --- a/pkg/decoder/core.go +++ b/pkg/decoder/core.go @@ -20,7 +20,6 @@ import "C" import ( "fmt" "log/slog" - "os" "sync" "unsafe" ) @@ -29,14 +28,6 @@ type VideoDecoder struct { handle C.EZ_DECODER_HANDLE sync.RWMutex IsStart bool - - file *os.File -} - -func (v *VideoDecoder) WriteFile(data string) { - if v.file != nil { - v.file.WriteString(data) - } } func (v *VideoDecoder) Create() error { @@ -45,16 +36,6 @@ func (v *VideoDecoder) Create() error { if v.IsStart { return nil } - //if v.file == nil { - // filename := filepath.Join(utils.CWD(), "logs", fmt.Sprintf("%016p_decoder.log", v)) - // file, err := os.OpenFile(filename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, os.ModePerm) - // if err == nil { - // v.file = file - // v.file.WriteString("↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓\r\n") - // } else { - // llog.Info("创建文件失败:%s", filename) - // } - //} ret := int(C.ez_decoder_create(&v.handle)) v.IsStart = true return IsReturnErrorf(ret, "ez_decoder_create") @@ -67,43 +48,29 @@ func (v *VideoDecoder) Destroy() error { return nil } v.IsStart = false - if v.file != nil { - v.file.WriteString("↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑\r\n") - v.file.Close() - v.file = nil - } slog.Info("====>>ez_decoder_destroy", "v", v) ret := int(C.ez_decoder_destroy(&v.handle)) return IsReturnErrorf(ret, "ez_decoder_destroy") } -func (v *VideoDecoder) PushDataEx(buf uintptr, size, codec int) (w, h int, data []byte, err error) { +func (v *VideoDecoder) PushDataEx(buf []byte, codec int) (w, h int, data []byte, err error) { v.Lock() defer v.Unlock() if !v.IsStart { return 0, 0, nil, fmt.Errorf("decoder fail") } - if v.file != nil { - v.file.WriteString(fmt.Sprintf("[start] function:PushData;buf len:%d; codec:%x\r\n", size, codec)) - } + cBytes := C.CBytes(buf) + defer C.free(unsafe.Pointer(cBytes)) // 必须手动释放 info := &C.EZDecoderInfo{} var out_data *C.uchar - //llog.Info("=======================>>>>>>:%v:%d", buf, size) - ret := int(C.ez_decoder_push_data(v.handle, (*C.uchar)(unsafe.Pointer(buf)), C.int(size), C.int(codec), &out_data, info)) - //var out_data uintptr - //ret := int(C.ez_decoder_push_data(v.handle, (*C.char)(unsafe.Pointer(buf)), C.int(size), C.int(codec), (**C.char)(unsafe.Pointer(&out_data)), info)) - if v.file != nil { - v.file.WriteString(fmt.Sprintf("[end] function:PushData;buf len:%d; codec:%x; ret:%d\r\n", size, codec, ret)) - } + ret := int(C.ez_decoder_push_data(v.handle, (*C.uchar)(cBytes), C.int(len(buf)), C.int(codec), &out_data, info)) //return 0, 0, nil, fmt.Errorf("test") // 252 if ret == 0 { data_size := int(info.data_size) _w := int(info.width) _h := int(info.height) - //llog.Info("=======================>>>>>>789:%v:%p:%d", out_data, v, data_size) yuv := C.GoBytes(unsafe.Pointer(out_data), info.data_size) _data := make([]byte, data_size) copy(_data, yuv) - //llog.Info("=======================>>>>>>22222") return _w, _h, _data, nil //_data := make([]byte, data_size) diff --git a/pkg/vqdcms/core.go b/pkg/vqdcms/core.go index 05bbb7c..7759101 100644 --- a/pkg/vqdcms/core.go +++ b/pkg/vqdcms/core.go @@ -9,7 +9,7 @@ type VqdTaskMap struct { sync.RWMutex } -// 读出相应Key的子Map +// 读出相应Key的Map func (m *VqdTaskMap) LoadTaskMap(key string) (value *VQDHandle, ok bool) { m.RLock() defer m.RUnlock() @@ -17,16 +17,25 @@ func (m *VqdTaskMap) LoadTaskMap(key string) (value *VQDHandle, ok bool) { return } -// 删除相应Key的子Map +// 删除相应Key的Map func (m *VqdTaskMap) DeleteTaskMap(key string) { m.Lock() defer m.Unlock() delete(m.M, key) } -// 增加或修改相应Key的子Map +// 增加或修改相应Key的Map func (m *VqdTaskMap) StoreChildMap(key string, value *VQDHandle) { m.Lock() defer m.Unlock() m.M[key] = value } + +// 删除全部的Map +func (m *VqdTaskMap) DeleteTaskMapAll() { + m.RLock() + defer m.RUnlock() + for _, handle := range m.M { + handle.Destroy() + } +} diff --git a/pkg/vqdcms/mode.go b/pkg/vqdcms/mode.go index 8551d50..1be0304 100644 --- a/pkg/vqdcms/mode.go +++ b/pkg/vqdcms/mode.go @@ -2,10 +2,13 @@ package vqdcms import ( "database/sql/driver" + "easyvqd/internal/core/vqd" "encoding/json" "time" ) +const VQD_IMAGES_DIR = "snap" + type Abnormal struct { Value float32 `json:"value"` Name string `json:"name"` @@ -43,11 +46,13 @@ func (r defaultValue) Value() (driver.Value, error) { type AbnormalModel struct { ID int `gorm:"primary_key" json:"id"` + TaskName string `json:"task_name"` ChannelID string `json:"channel_id"` ChannelName string `json:"channel_name"` - AlgoID int `json:"algo_id"` - AlgoName string `json:"algo_name"` - TaskName string `json:"task_name"` + PlanID int `json:"plan_id"` + PlanName string `json:"plan_name"` + TemplateID int `json:"template_id"` + TemplateName string `json:"template_name"` FilePath string `json:"file_path"` IsDeep bool `json:"is_deep"` Abnormals abnormal `gorm:"type:json" json:"abnormals"` @@ -267,58 +272,95 @@ type VQDPara struct { FlowerPara VQDFlowerPara /* 花屏检测 */ } -// config cvrdo.VQDAlgoConfig -func NewVQDPara() VQDPara { +// config +func NewVQDPara(tem *vqd.VqdTaskTemplate) VQDPara { + var strFunc []string + if tem.VqdConfig.Enable { + strFunc = append(strFunc, "vqd_config") + } + if tem.VqdLgtDark.Enable { + strFunc = append(strFunc, "vqd_lgt_dark") + } + if tem.VqdBlue.Enable { + strFunc = append(strFunc, "vqd_blue") + } + if tem.VqdClarity.Enable { + strFunc = append(strFunc, "vqd_clarity") + } + if tem.VqdShark.Enable { + strFunc = append(strFunc, "vqd_shark") + } + if tem.VqdFreeze.Enable { + strFunc = append(strFunc, "vqd_freeze") + } + if tem.VqdColor.Enable { + strFunc = append(strFunc, "vqd_color") + } + if tem.VqdOcclusion.Enable { + strFunc = append(strFunc, "vqd_occlusion") + } + if tem.VqdNoise.Enable { + strFunc = append(strFunc, "vqd_noise") + } + if tem.VqdContrast.Enable { + strFunc = append(strFunc, "vqd_contrast") + } + if tem.VqdMosaic.Enable { + strFunc = append(strFunc, "vqd_mosaic") + } + if tem.VqdFlower.Enable { + strFunc = append(strFunc, "vqd_flower") + } + EnableFunc := GetAlgoEnable(strFunc) return VQDPara{ - VecFrmNum: 2, - UseDeepLearning: true, - EnableFunc: GetAlgoEnable([]string{"vqd_config", "vqd_lgt_dark", "vqd_blue", "vqd_clarity", "vqd_shark", "vqd_freeze", - "vqd_color", "vqd_occlusion", "vqd_noise", "vqd_contrast", "vqd_mosaic", "vqd_flower"}), + VecFrmNum: int(tem.VqdConfig.FrmNum), + UseDeepLearning: tem.VqdConfig.IsDeepLearn, + EnableFunc: EnableFunc, ColorPara: VQDColorPara{ - ColorThr: 0.3, - ColorAbnNumRatio: 0.6, + ColorThr: tem.VqdColor.ColorThr, + ColorAbnNumRatio: tem.VqdColor.ColorAbnNumRatio, }, LgtDarkPara: VQDLgtDarkPara{ - LightThr: 0.3, - DarkThr: 0.4, - LgtDarkAbnNumRatio: 0.6, + LightThr: tem.VqdLgtDark.LgtThr, + DarkThr: tem.VqdLgtDark.DarkThr, + LgtDarkAbnNumRatio: tem.VqdLgtDark.LgtDarkAbnNumRatio, }, ClarityPara: VQDClarityPara{ - ClarityThr: 0.5, - ClarityAbnNumRatio: 0.6, + ClarityThr: tem.VqdClarity.ClarityThr, + ClarityAbnNumRatio: tem.VqdClarity.ClarityAbnNumRatio, }, NoisePara: VQDNoisePara{ - NoiseThr: 0.3, - NoiseAbnNumRatio: 0.6, + NoiseThr: tem.VqdNoise.NoiseThr, + NoiseAbnNumRatio: tem.VqdNoise.NoiseAbnNumRatio, }, ContrastPara: VQDContrastPara{ - CtraLowThr: 0.3, - CtraHighThr: 0.8, - CtraAbnNumRatio: 0.6, + CtraLowThr: tem.VqdContrast.CtraLowThr, + CtraHighThr: tem.VqdContrast.CtraHighThr, + CtraAbnNumRatio: tem.VqdContrast.CtraAbnNumRatio, }, OcclusionPara: VQDOcclusionPara{ - OcclusionThr: 0.1, - OcclusionAbnNumRatio: 0.6, + OcclusionThr: tem.VqdOcclusion.OcclusionThr, + OcclusionAbnNumRatio: tem.VqdOcclusion.OcclusionAbnNumRatio, }, BluePara: VQDBluePara{ - BlueThr: 0.3, - BlueAbnNumRatio: 0.6, + BlueThr: tem.VqdBlue.BlueThr, + BlueAbnNumRatio: tem.VqdBlue.BlueAbnNumRatio, }, SharkPara: VQDSharkPara{ - SharkThr: 0.2, - SharkAbnNumRatio: 0.2, + SharkThr: tem.VqdShark.SharkThr, + SharkAbnNumRatio: tem.VqdShark.SharkAbnNumRatio, }, FreezePara: VQDFreezePara{ - FreezeThr: 0.999, - FreezeAbnNumRatio: 0.99, + FreezeThr: tem.VqdFreeze.FreezeThr, + FreezeAbnNumRatio: tem.VqdFreeze.FreezeAbnNumRatio, }, MosaicPara: VQDMosaicPara{ - MosaicThr: 0.3, - MosaicAbnNumRatio: 0.6, + MosaicThr: tem.VqdMosaic.MosaicThr, + MosaicAbnNumRatio: tem.VqdMosaic.MosaicAbnNumRatio, }, FlowerPara: VQDFlowerPara{ - FlowerThr: 0.3, - FlowerAbnNumRatio: 0.6, + FlowerThr: tem.VqdFlower.FlowerThr, + FlowerAbnNumRatio: tem.VqdFlower.FlowerAbnNumRatio, }, } } diff --git a/pkg/vqdcms/type.go b/pkg/vqdcms/type.go index 5163db7..aa85922 100644 --- a/pkg/vqdcms/type.go +++ b/pkg/vqdcms/type.go @@ -1,5 +1,12 @@ package vqdcms +type VqdTaskStatus int + +const ( + TaskStatusStopped VqdTaskStatus = iota + TaskStatusRunning + TaskStatusFailed +) const NXU_VQD_DISABLE_ALL = 0x00000000 /* 所有功能关闭 */ const NXU_VQD_ENABLE_COLOR = 0x00000001 /* 偏色检测 */ const NXU_VQD_ENABLE_LGTDARK = 0x00000002 /* 过亮过暗检测 */ diff --git a/pkg/vqdcms/vqd.go b/pkg/vqdcms/vqd.go index 1fbd3fd..b1842f7 100644 --- a/pkg/vqdcms/vqd.go +++ b/pkg/vqdcms/vqd.go @@ -7,9 +7,10 @@ package vqdcms */ import "C" import ( + "context" + "easyvqd/internal/core/host" "easyvqd/pkg/decoder" "fmt" - "github.com/shirou/gopsutil/v4/mem" "log/slog" "os" "path/filepath" @@ -29,20 +30,33 @@ type VideoInfoVQD struct { Params VQDPara IsCreateSuccess bool } - +type VQDHandleInfo struct { + TaskID int + Plans string + TaskName string + ChannelID string + ChannelName string + PlanID int + PlanName string + TemplateID int + TemplateName string +} type VQDHandle struct { - running uint32 - TaskID int // 标识ID - ChnID string // 通道ID - Plans string // 任务计划 - data chan ChanData - dataLock sync.RWMutex - cb VQDResultCB - handle *VideoInfoVQD - name string // 算法名称 - file *os.File - decoder *decoder.VideoDecoder - fileLock sync.RWMutex + running uint32 + ID int // 标识ID + info VQDHandleInfo + plansLock sync.RWMutex + playTicker *Scheduler + ErrorMsg string + data chan ChanData + dataLock sync.RWMutex + cb VQDResultCB + handle *VideoInfoVQD + name string // 算法名称 + decoder *decoder.VideoDecoder + fileLock sync.RWMutex + hostCore *host.Core + Status VqdTaskStatus } type VQDResultCB func(AbnormalModel) @@ -54,53 +68,91 @@ type ChanData struct { now time.Time } -func IsCurTimeInRecordPlan(recordPlanNew string, now time.Time) bool { +// IsCurTimeInRecordPlan 根据一周每天每小时的开关状态判断当前时间是否为开启状态 +func IsCurTimeInRecordPlan(schedule string, now time.Time) bool { + if len(schedule) != 7*24 { + slog.Error("schedule length is not 7*24", "schedule", schedule) + return false + } + dayOfWeek := int(now.Weekday()+6) % 7 // 调整为0-6,对应周一至周日 + hourOfDay := now.Hour() + + // 计算位置索引 + index := (dayOfWeek * 24) + hourOfDay + + // 检查索引位置的字符是否为'1' + if index >= 0 && index < len(schedule) && schedule[index] == '1' { + return true + } return false } -func NewVQDHandle(cb VQDResultCB, taskId int, chnId string) *VQDHandle { +func NewVQDHandle(cb VQDResultCB, hostCore *host.Core, info VQDHandleInfo) *VQDHandle { v := &VQDHandle{ - running: 0, - decoder: &decoder.VideoDecoder{}, - ChnID: chnId, - TaskID: taskId, - data: make(chan ChanData, MAX_STREAM_CHAN_NUM), - cb: cb, - handle: &VideoInfoVQD{}, + running: 0, + decoder: &decoder.VideoDecoder{}, + info: info, + ID: info.TaskID, + data: make(chan ChanData, MAX_STREAM_CHAN_NUM), + cb: cb, + handle: &VideoInfoVQD{}, + playTicker: NewScheduler(), + hostCore: hostCore, + Status: TaskStatusStopped, } err := v.decoder.Create() if err != nil { - slog.Error("decoder Create ", "taskId", chnId, "err", err) + slog.Error("decoder Create ", "taskId", info.ChannelID, "err", err) } + v.StartPlay() return v } -func (v *VQDHandle) SetVQDConfig(params VQDPara) error { +func (v *VQDHandle) SetVQDConfig(params VQDPara, info VQDHandleInfo) error { + v.plansLock.Lock() + v.info = info + v.plansLock.Unlock() return v.handle.Config(params, params.EnableFunc, 10) } func (v *VQDHandle) GetHandle() *VideoInfoVQD { return v.handle } -func (v *VQDHandle) Create(params VQDPara) *VQDHandle { +func (v *VQDHandle) StartPlay() { + v.Play() + v.playTicker.Start(25*time.Second, func() { + v.Play() + }) +} +func (v *VQDHandle) Play() { + if IsCurTimeInRecordPlan(v.info.Plans, time.Now()) { + //slog.Info("vqd cms play", "taskId", v.TaskID, "chnId", v.ChnID) + _, errs := v.hostCore.Play(context.TODO(), &host.PlayInput{ + ChannelID: v.info.ChannelID, + ActiveSecond: 0, + }) + if errs != nil { + slog.Debug("vqd cms play", "taskId", v.info.TaskID, "chnId", v.info.ChannelID, "err", errs) + v.ErrorMsg = errs.Error() + v.Status = TaskStatusFailed + } else { + v.ErrorMsg = "" + v.Status = TaskStatusRunning + } + } else { + v.Status = TaskStatusStopped + } +} +func (v *VQDHandle) Create(params VQDPara, plan string) *VQDHandle { + v.plansLock.Lock() + v.info.Plans = plan + v.plansLock.Unlock() if atomic.LoadUint32(&v.running) == 1 { return v } - //if v.file == nil { - // filename := filepath.Join(utils.CWD(), "logs", fmt.Sprintf("%016p_vqdhandle.log", v)) - // file, err := os.OpenFile(filename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, os.ModePerm) - // if err == nil { - // v.fileLock.Lock() - // v.file = file - // v.file.WriteString("↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓\r\n") - // v.fileLock.Unlock() - // } else { - // llog.Info("创建文件失败:%s", filename) - // } - //} err := v.handle.Create(params, params.EnableFunc, 10) if err != nil { - //llog.Info("vqd create fail:%s", err.Error()) + slog.Error("vqd create", "taskId", v.info.TaskID, "chnId", v.info.ChannelID, "fail", err) return v } atomic.StoreUint32(&v.running, 1) @@ -109,16 +161,17 @@ func (v *VQDHandle) Create(params VQDPara) *VQDHandle { } func (v *VQDHandle) Destroy() { - if v.file != nil { - v.fileLock.Lock() - v.file.WriteString("↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑\r\n") - v.file.Close() - v.file = nil - v.fileLock.Unlock() - } if v.data != nil { close(v.data) } + if v.decoder != nil { + err := v.decoder.Destroy() + if err != nil { + slog.Error("vqd decoder destroy", "taskId", v.info.TaskID, "chnId", v.info.ChannelID, "err", err) + } + } + v.decoder = nil + v.playTicker.Stop() v.data = nil if atomic.LoadUint32(&v.running) == 1 { atomic.StoreUint32(&v.running, 0) @@ -133,10 +186,10 @@ func (v *VQDHandle) RunFrame() { print(fmt.Sprintf("%s\n", string(debug.Stack()))) } }() - cvqdImgsDir := filepath.Join(CWD(), "vqd_images", fmt.Sprintf("%d", v.TaskID)) + cvqdImgsDir := filepath.Join(CWD(), VQD_IMAGES_DIR, fmt.Sprintf("%d", v.info.TaskID)) cvqdImgsDir = filepath.ToSlash(cvqdImgsDir) if err := os.MkdirAll(cvqdImgsDir, os.ModePerm); err != nil { - //llog.Info("%s:%s", cvqdImgsDir, err.Error()) + slog.Error("vqd create img dir", "taskId", v.info.TaskID, "chnId", v.info.ChannelID, "err", err) } hyper := 0 @@ -149,7 +202,7 @@ func (v *VQDHandle) RunFrame() { return } if len(v.data) >= (MAX_STREAM_CHAN_NUM - 6) { - //llog.Info("vqd channel num >= %d", MAX_STREAM_CHAN_NUM) + slog.Error("vqd channel num", "taskId", v.info.TaskID, "chnId", v.info.ChannelID, ">= ", MAX_STREAM_CHAN_NUM) hyper = MAX_STREAM_CHAN_NUM / 10 } if hyper > 0 { @@ -157,16 +210,11 @@ func (v *VQDHandle) RunFrame() { break } cdata = data - //if !IsCurTimeInRecordPlan(v.analysisTime, cdata.now) { - // continue - //} - if v.file != nil { - v.fileLock.Lock() - v.file.WriteString(fmt.Sprintf("vqd START data[%d] w[%d] h[%d]\r\n", len(cdata.data), cdata.w, cdata.h)) - v.fileLock.Unlock() + if !IsCurTimeInRecordPlan(v.info.Plans, cdata.now) { + continue } now := time.Now().UnixMilli() - fpath := filepath.Join(cvqdImgsDir, fmt.Sprintf("%d_%s_%d.jpg", v.TaskID, v.ChnID, now)) + fpath := filepath.Join(cvqdImgsDir, fmt.Sprintf("%s_%d_%d_%d.jpg", v.info.ChannelID, v.info.TemplateID, v.info.PlanID, now)) fpath = filepath.ToSlash(fpath) result := VQDResult{} ret := v.handle.Frame(cdata.data, cdata.w, cdata.h, index, fpath, &result) @@ -174,8 +222,15 @@ func (v *VQDHandle) RunFrame() { index = 0 if value, b := v.parseVQD(result); b { value.FilePath = strings.TrimPrefix(filepath.ToSlash(fpath), filepath.ToSlash(CWD())) - value.ChannelID = v.ChnID - value.AlgoName = v.name + value.TaskName = v.info.TaskName + value.ID = v.info.TaskID + value.ChannelID = v.info.ChannelID + value.ChannelName = v.info.ChannelName + value.PlanID = v.info.PlanID + value.PlanName = v.info.PlanName + value.TemplateID = v.info.TemplateID + value.TemplateName = v.info.TemplateName + if v.cb != nil { v.cb(value) } else { @@ -184,18 +239,6 @@ func (v *VQDHandle) RunFrame() { } } } - if v.file != nil { - me, err := mem.VirtualMemory() - if err != nil { - v.fileLock.Lock() - v.file.WriteString(fmt.Sprintf("vqd END ret[%d] mem is nil\r\n", ret)) - v.fileLock.Unlock() - } else { - v.fileLock.Lock() - v.file.WriteString(fmt.Sprintf("vqd END ret[%d] mem[%.2f%s]\r\n", ret, me.UsedPercent, "%")) - v.fileLock.Unlock() - } - } //C.free(fp) //C.free(yuvBuf) index++ @@ -368,14 +411,10 @@ func (v *VQDHandle) parseVQD(result VQDResult) (AbnormalModel, bool) { return abnormals, isabnormal } func (v *VQDHandle) SendData(buf []byte, _codec int) { - dataP := GetIFramePointer(buf) - if dataP == nil || !dataP.IsValid { - slog.Error("I帧转指针失败: ", "TaskID", v.TaskID) - return - } - w, h, data, err := v.decoder.PushDataEx(uintptr(dataP.Pointer), dataP.Length, _codec) + + w, h, data, err := v.decoder.PushDataEx(buf, _codec) if err != nil { - slog.Error("I帧转YUV失败: ", "TaskID", v.TaskID, "err", err) + slog.Error("I帧转YUV失败: ", "TaskID", v.info.TaskID, "err", err) return } if len(data) > 0 && v.data != nil { @@ -388,9 +427,6 @@ func (v *VQDHandle) SendData(buf []byte, _codec int) { h: h, now: now, } - //v.dataLock.Lock() - //v.data = append(v.data, d) - //v.dataLock.Unlock() v.data <- d } } diff --git a/pkg/vqdcms/worker.go b/pkg/vqdcms/worker.go new file mode 100644 index 0000000..8a6e61f --- /dev/null +++ b/pkg/vqdcms/worker.go @@ -0,0 +1,56 @@ +package vqdcms + +import ( + "context" + "time" +) + +type Worker struct { + ctx context.Context + cancel context.CancelFunc +} + +func NewWorker() *Worker { + ctx, cancel := context.WithCancel(context.Background()) + return &Worker{ + ctx: ctx, + cancel: cancel, + } +} + +func (w *Worker) Stop() { + w.cancel() // 显式调用cancel停止worker +} + +type Scheduler struct { + stopChan chan struct{} +} + +func NewScheduler() *Scheduler { + return &Scheduler{ + stopChan: make(chan struct{}), + } +} + +func (s *Scheduler) Start(interval time.Duration, task func()) { + go func() { + ticker := time.NewTicker(interval) + defer ticker.Stop() + + for { + select { + case <-ticker.C: + task() + case <-s.stopChan: + return + } + } + }() +} + +func (s *Scheduler) Stop() { + if s.stopChan != nil { + close(s.stopChan) + s.stopChan = nil // 防止重复关闭 + } +} diff --git a/web/src/components/AddVqdTask.tsx b/web/src/components/AddVqdTask.tsx index f2ccf56..f079d98 100644 --- a/web/src/components/AddVqdTask.tsx +++ b/web/src/components/AddVqdTask.tsx @@ -36,6 +36,7 @@ const AddVqdTask = forwardRef( channel_id: task.channel_id, channel_name: task.channel_name, task_template_id: task.task_template_id, + time_template_id: task.time_template_id, task_template_name: task.task_template_name, enable: task.enable, }; diff --git a/web/src/components/AddVqdTaskTemplate.tsx b/web/src/components/AddVqdTaskTemplate.tsx index ff7b08c..957f140 100644 --- a/web/src/components/AddVqdTaskTemplate.tsx +++ b/web/src/components/AddVqdTaskTemplate.tsx @@ -114,7 +114,7 @@ const AddVqdTaskTemplate = forwardRef { - message.success("创建任务成功"); + message.success("创建成功"); handleClose(); onSuccess?.(); }, @@ -124,7 +124,7 @@ const AddVqdTaskTemplate = forwardRef { - message.success("更新任务成功"); + message.success("更新成功"); handleClose(); onSuccess?.(); }, diff --git a/web/src/components/AddVqdTimeTemplate.tsx b/web/src/components/AddVqdTimeTemplate.tsx index 4a15a07..856b559 100644 --- a/web/src/components/AddVqdTimeTemplate.tsx +++ b/web/src/components/AddVqdTimeTemplate.tsx @@ -167,7 +167,7 @@ const AddVqdTimeTemplate = forwardRef { - message.success("创建任务成功"); + message.success("创建成功"); handleClose(); onSuccess?.(); }, @@ -177,7 +177,7 @@ const AddVqdTimeTemplate = forwardRef { - message.success("更新任务成功"); + message.success("更新成功"); handleClose(); onSuccess?.(); }, diff --git a/web/src/components/VqdAlarm.tsx b/web/src/components/VqdAlarm.tsx index bc7f6d0..ba6a195 100644 --- a/web/src/components/VqdAlarm.tsx +++ b/web/src/components/VqdAlarm.tsx @@ -1,6 +1,6 @@ import { useRef, useState, useMemo } from "react"; -import { Table, Button, Space, Popconfirm, Flex, message, Tooltip, Select, Row, Col, Empty, Pagination, Tag } from "antd"; -import { EditOutlined, DeleteOutlined, PlusOutlined } from "@ant-design/icons"; +import { Table, Button, Space, Popconfirm, Flex, message, Tooltip, Select, Row, Col, Empty, Pagination, Tag, Popover } from "antd"; +import { EditOutlined, DeleteOutlined, InfoCircleOutlined } from "@ant-design/icons"; import { useQuery, useMutation } from "@tanstack/react-query"; import { GetVqdAlarm, DeleteVqdAlarm, DeleteVqdAlarmAll } from "../api/vqdalarm"; import type { VqdAlarmItem } from "../types/vqdalarm"; @@ -233,7 +233,7 @@ export default function VqdAlarmPage() { loading={moonLoading} /> */}
-