调整告警图片展示

This commit is contained in:
Sake 2026-01-27 10:42:21 +08:00
parent 83a50fa245
commit 011694d7a7
33 changed files with 856 additions and 400 deletions

2
.gitignore vendored
View File

@ -42,7 +42,7 @@ tables/
*.pprof *.pprof
*.test *.test
snap/* snap/*
*buf264/*
# Logs # Logs
logs logs
*.log *.log

View File

@ -37,7 +37,7 @@
# 连续分析帧数(2-64), 默认为10, 最大为 64 # 连续分析帧数(2-64), 默认为10, 最大为 64
FrmNum = 10 FrmNum = 10
# 是否使用深度学习版本, 默认使用深度学习版本 # 是否使用深度学习版本, 默认使用深度学习版本
IsDeepLearn = false IsDeepLearn = true
[VqdLgtDark] [VqdLgtDark]
# 默认 0.4, 取值范围: 0~1, 建议范围: 0.2~0.6 # 默认 0.4, 取值范围: 0~1, 建议范围: 0.2~0.6

View File

@ -111,69 +111,69 @@ type VqdConfig struct {
// 亮度检测 // 亮度检测
type VqdLgtDark struct { type VqdLgtDark struct {
DarkThr float64 `json:"dark_thr" comment:"默认 0.4, 取值范围: 0~1, 建议范围: 0.2~0.6"` DarkThr float32 `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"` LgtThr float32 `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"` LgtDarkAbnNumRatio float32 `json:"lgt_dark_abn_num_ratio" comment:"默认为0.5, 取值范围: 0~1, 建议范围: 0.1~0.9"`
} }
// 蓝屏检查 // 蓝屏检查
type VqdBlue struct { type VqdBlue struct {
BlueThr float64 `json:"blue_thr" comment:"默认为 0.6, 取值范围: 0~1, 建议范围 0.4~0.9"` BlueThr float32 `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"` BlueAbnNumRatio float32 `json:"blue_abn_num_ratio" comment:"默认为0.5, 取值范围: 0~1, 建议范围: 0.1~0.9"`
} }
// 清晰度检查 // 清晰度检查
type VqdClarity struct { type VqdClarity struct {
ClarityThr float64 `json:"clarity_thr" comment:"默认为0.4, 取值范围: 0~1, 建议范围: 0.3~0.99"` ClarityThr float32 `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"` ClarityAbnNumRatio float32 `json:"clarity_abn_num_ratio" comment:"默认为0.5, 取值范围: 0~1, 建议范围: 0.1~0.9"`
} }
// 抖动检查 // 抖动检查
type VqdShark struct { type VqdShark struct {
SharkThr float64 `json:"shark_thr" comment:"默认为 0.2, 取值范围: 0~1, 建议范围: 0.1~0.8"` SharkThr float32 `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"` SharkAbnNumRatio float32 `json:"shark_abn_num_ratio" comment:"默认为0.2, 取值范围: 0~1, 建议范围: 0.1~0.6"`
} }
// 冻结检测 // 冻结检测
type VqdFreeze struct { type VqdFreeze struct {
FreezeThr float64 `json:"freeze_thr" comment:"默认 0.4, 取值范围: 0~1, 建议范围: 0.2~0.6"` FreezeThr float32 `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"` FreezeAbnNumRatio float32 `json:"freeze_abn_num_ratio" comment:"默认为0.99, 取值范围: 0.8~1, 建议范围: 0.95~1"`
} }
// 偏色检测 // 偏色检测
type VqdColor struct { type VqdColor struct {
ColorThr float64 `json:"color_thr" comment:"默认为0.18, 取值范围: 0~1, 建议范围: 0.1~0.5"` ColorThr float32 `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"` ColorAbnNumRatio float32 `json:"color_abn_num_ratio" comment:"默认为0.5, 取值范围: 0~1, 建议范围: 0.3~0.9"`
} }
// 遮挡检测 // 遮挡检测
type VqdOcclusion struct { type VqdOcclusion struct {
OcclusionThr float64 `json:"occlusion_thr" comment:"默认为0.1, 取值范围: 0~1, 建议范围: 0.05~0.5"` OcclusionThr float32 `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"` OcclusionAbnNumRatio float32 `json:"occlusion_abn_num_ratio" comment:"默认为0.5, 取值范围: 0~1, 建议范围: 0.3~0.9"`
} }
// 噪声检测 // 噪声检测
type VqdNoise struct { type VqdNoise struct {
NoiseThr float64 `json:"noise_thr" comment:"默认为 0.3, 取值范围: 0~1, 建议范围: 0.2~0.8"` NoiseThr float32 `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"` NoiseAbnNumRatio float32 `json:"noise_abn_num_ratio" comment:"默认为0.6, 取值范围: 0~1, 建议范围: 0.3~0.9"`
} }
// 对比度检测 // 对比度检测
type VqdContrast struct { type VqdContrast struct {
CtraLowThr float64 `json:"ctra_low_thr" comment:"默认为 0.2, 取值范围: 0~1, 建议范围: 0.1~0.3"` CtraLowThr float32 `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"` CtraHighThr float32 `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"` CtraAbnNumRatio float32 `json:"ctra_abn_num_ratio" comment:"默认为0.5, 取值范围: 0~1, 建议范围: 0.3~0.9"`
} }
// 马赛克检测 // 马赛克检测
type VqdMosaic struct { type VqdMosaic struct {
MosaicThr float64 `json:"mosaic_thr" comment:"默认为 0.1 取值范围: 0~1, 建议范围: 0.1~0.9"` MosaicThr float32 `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"` MosaicAbnNumRatio float32 `json:"mosaic_abn_num_ratio" comment:"默认为0.5,取值范围: 0~1, 建议范围: 0.3"`
} }
// 花屏检测 // 花屏检测
type VqdFlower struct { type VqdFlower struct {
FlowerThr float64 `json:"flower_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 float64 `json:"flower_abn_num_ratio" comment:"默认为0.6, 取值范围: 0~1, 建议范围: 0.3"` FlowerAbnNumRatio float32 `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"` MosaicThr float32 `json:"mosaic_thr" comment:"默认为 0.3 取值范围: 0~1, 建议范围: 0.1~0.9"`
} }

View File

@ -52,8 +52,8 @@ func NewCore(cfg *conf.Bootstrap) *Core {
} }
sdk.AddResponseHandler("stop", core.stop) sdk.AddResponseHandler("stop", core.stop)
sdk.AddResponseHandler("ping", core.ping) sdk.AddResponseHandler("ping", core.ping)
// 这部分都是收到响应后的回调 // 这部分都是收到响应后的回调
sdk.AddResponseHandler("play", core.playRespH)
sdk.AddResponseHandler("findDevices", core.findDevicesRespH) sdk.AddResponseHandler("findDevices", core.findDevicesRespH)
sdk.AddResponseHandler("findChannels", core.findChannelsRespH) sdk.AddResponseHandler("findChannels", core.findChannelsRespH)
sdk.AddResponseHandler("getBaseConfig", core.getBaseConfigRespH) 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) slog.Debug("Received 'findTalkUrl' from host", "request_id", requestID, "args", args)
return nil, nil 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) { func (c Core) iframeDataRespH(requestID string, args json.RawMessage) (interface{}, error) {
slog.Debug("Received 'iframeData' from host", "request_id", requestID, "args", args) slog.Debug("Received 'iframeData' from host", "request_id", requestID, "args", args)
return nil, nil return nil, nil

View File

@ -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
}

View File

@ -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"`
}

View File

@ -60,9 +60,9 @@ func (i *VqdConfig) Scan(input interface{}) error {
// 亮度检测 // 亮度检测
type VqdLgtDark struct { type VqdLgtDark struct {
Enable bool `gorm:"column:enable;notNull;default:FALSE;comment:启用" json:"enable"` // 启用 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 DarkThr float32 `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 LgtThr float32 `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 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) { func (a VqdLgtDark) Value() (driver.Value, error) {
@ -75,8 +75,8 @@ func (i *VqdLgtDark) Scan(input interface{}) error {
// 蓝屏检查 // 蓝屏检查
type VqdBlue struct { type VqdBlue struct {
Enable bool `gorm:"column:enable;notNull;default:FALSE;comment:启用" json:"enable"` // 启用 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 BlueThr float32 `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 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) { func (a VqdBlue) Value() (driver.Value, error) {
@ -89,8 +89,8 @@ func (i *VqdBlue) Scan(input interface{}) error {
// 清晰度检查 // 清晰度检查
type VqdClarity struct { type VqdClarity struct {
Enable bool `gorm:"column:enable;notNull;default:FALSE;comment:启用" json:"enable"` // 启用 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 ClarityThr float32 `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 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) { func (a VqdClarity) Value() (driver.Value, error) {
@ -103,8 +103,8 @@ func (i *VqdClarity) Scan(input interface{}) error {
// 抖动检查 // 抖动检查
type VqdShark struct { type VqdShark struct {
Enable bool `gorm:"column:enable;notNull;default:FALSE;comment:启用" json:"enable"` // 启用 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 SharkThr float32 `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 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) { func (a VqdShark) Value() (driver.Value, error) {
@ -117,8 +117,8 @@ func (i *VqdShark) Scan(input interface{}) error {
// 冻结检测 // 冻结检测
type VqdFreeze struct { type VqdFreeze struct {
Enable bool `gorm:"column:enable;notNull;default:FALSE;comment:启用" json:"enable"` // 启用 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 FreezeThr float32 `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 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) { func (a VqdFreeze) Value() (driver.Value, error) {
@ -131,8 +131,8 @@ func (i *VqdFreeze) Scan(input interface{}) error {
// 偏色检测 // 偏色检测
type VqdColor struct { type VqdColor struct {
Enable bool `gorm:"column:enable;notNull;default:FALSE;comment:启用" json:"enable"` // 启用 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 ColorThr float32 `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 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) { func (a VqdColor) Value() (driver.Value, error) {
@ -145,8 +145,8 @@ func (i *VqdColor) Scan(input interface{}) error {
// 遮挡检测 // 遮挡检测
type VqdOcclusion struct { type VqdOcclusion struct {
Enable bool `gorm:"column:enable;notNull;default:FALSE;comment:启用" json:"enable"` // 启用 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 OcclusionThr float32 `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 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) { func (a VqdOcclusion) Value() (driver.Value, error) {
@ -159,8 +159,8 @@ func (i *VqdOcclusion) Scan(input interface{}) error {
// 噪声检测 // 噪声检测
type VqdNoise struct { type VqdNoise struct {
Enable bool `gorm:"column:enable;notNull;default:FALSE;comment:启用" json:"enable"` // 启用 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 NoiseThr float32 `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 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) { func (a VqdNoise) Value() (driver.Value, error) {
@ -173,9 +173,9 @@ func (i *VqdNoise) Scan(input interface{}) error {
// 对比度检测 // 对比度检测
type VqdContrast struct { type VqdContrast struct {
Enable bool `gorm:"column:enable;notNull;default:FALSE;comment:启用" json:"enable"` // 启用 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 CtraLowThr float32 `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 CtraHighThr float32 `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 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) { func (a VqdContrast) Value() (driver.Value, error) {
@ -188,8 +188,8 @@ func (i *VqdContrast) Scan(input interface{}) error {
// 马赛克检测 // 马赛克检测
type VqdMosaic struct { type VqdMosaic struct {
Enable bool `gorm:"column:enable;notNull;default:FALSE;comment:启用" json:"enable"` // 启用 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 MosaicThr float32 `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 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) { func (a VqdMosaic) Value() (driver.Value, error) {
@ -202,9 +202,9 @@ func (i *VqdMosaic) Scan(input interface{}) error {
// 花屏检测 // 花屏检测
type VqdFlower struct { type VqdFlower struct {
Enable bool `gorm:"column:enable;notNull;default:FALSE;comment:启用" json:"enable"` // 启用 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 FlowerThr float32 `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 FlowerAbnNumRatio float32 `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 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) { func (a VqdFlower) Value() (driver.Value, error) {
@ -252,18 +252,50 @@ func (*VqdTimeTemplate) TableName() string {
return "vqd_time_template" 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 { type VqdAlarm struct {
orm.Model orm.Model
AlarmName string `gorm:"column:alarm_name;notNull;default:'';comment:告警名称" json:"alarm_name"` // 告警名称 IsDeep bool `gorm:"column:is_deep;notNull;default:FALSE;comment:启用" json:"is_deep"`
AlarmValue string `gorm:"column:alarm_value;notNull;default:'';comment:告警参数" json:"alarm_value"` // 告警参数
ChannelID string `gorm:"column:channel_id;notNull;default:'';comment:关联通道" json:"channel_id"` // 关联通道 ChannelID string `gorm:"column:channel_id;notNull;default:'';comment:关联通道" json:"channel_id"` // 关联通道
ChannelName string `gorm:"column:channel_name;notNull;default:'';comment:关联通道名称" json:"channel_name"` // 关联通道名称 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"` // 关联模板 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"` // 关联模板名称 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"` // 关联任务名称 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"` // 任务名称 TaskName string `gorm:"column:task_name;notNull;default:'';comment:关联任务名称" json:"task_name"` // 任务名称
FilePath string `gorm:"column:file_path;notNull;default:'';comment:文件路径" json:"file_path"` // 文件路径 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 // TableName database table name

View File

@ -34,9 +34,9 @@ func (c Core) FindVqdAlarmAll() ([]*VqdAlarm, int64, error) {
// FindVqdAlarm Paginated search // FindVqdAlarm Paginated search
func (c Core) FindVqdAlarm(ctx context.Context, in *FindVqdAlarmInput) ([]*VqdAlarm, int64, error) { func (c Core) FindVqdAlarm(ctx context.Context, in *FindVqdAlarmInput) ([]*VqdAlarm, int64, error) {
items := make([]*VqdAlarm, 0) items := make([]*VqdAlarm, 0)
if in.AlarmName != "" { if in.Name != "" {
query := orm.NewQuery(8). 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()...) total, err := c.store.VqdAlarm().Find(ctx, &items, in, query.Encode()...)
if err != nil { if err != nil {
return nil, 0, reason.ErrDB.Withf(`Find err[%s]`, err.Error()) return nil, 0, reason.ErrDB.Withf(`Find err[%s]`, err.Error())

View File

@ -7,7 +7,7 @@ import (
type FindVqdAlarmInput struct { type FindVqdAlarmInput struct {
web.PagerFilter web.PagerFilter
AlarmName string `form:"alarm_name"` // 名称 Name string `form:"name"` // 名称
} }
type EditVqdAlarmInput struct { type EditVqdAlarmInput struct {
@ -15,8 +15,8 @@ type EditVqdAlarmInput struct {
} }
type AddVqdAlarmInput struct { type AddVqdAlarmInput struct {
AlarmName string `json:"alarm_name"` // 告警名称 PlanID int64 `json:"plan_id"` // 关联计划
AlarmValue string `json:"alarm_value"` // 告警参数 PlanName string `json:"plan_name"` // 关联计划名称
ChannelID string `json:"channel_id"` // 关联通道 ChannelID string `json:"channel_id"` // 关联通道
ChannelName string `json:"channel_name"` // 关联通道名称 ChannelName string `json:"channel_name"` // 关联通道名称
TaskTemplateID int64 `json:"task_template_id"` // 关联模板 TaskTemplateID int64 `json:"task_template_id"` // 关联模板
@ -24,6 +24,9 @@ type AddVqdAlarmInput struct {
TaskID int64 `json:"task_id"` // 关联任务名称 TaskID int64 `json:"task_id"` // 关联任务名称
TaskName string `json:"task_name"` // 任务名称 TaskName string `json:"task_name"` // 任务名称
FilePath string `json:"file_path"` // 文件路径 FilePath string `json:"file_path"` // 文件路径
IsDeep bool `json:"is_deep"`
Abnormals Abnormals `json:"abnormals"` // 告警异常列表
DefaultValues DefaultValues `json:"default_values"` // 设置的默认阈值
} }
type DelVqdAlarmInput struct { type DelVqdAlarmInput struct {
IDs []int `json:"ids"` // id IDs []int `json:"ids"` // id

View File

@ -31,6 +31,34 @@ func (c Core) FindVqdTaskAll() ([]*VqdTask, int64, error) {
return items, total, nil 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 // FindVqdTask Paginated search
func (c Core) FindVqdTask(ctx context.Context, in *FindVqdTaskInput) ([]*VqdTask, int64, error) { func (c Core) FindVqdTask(ctx context.Context, in *FindVqdTaskInput) ([]*VqdTask, int64, error) {
items := make([]*VqdTask, 0) items := make([]*VqdTask, 0)
@ -64,6 +92,18 @@ func (c Core) FindVqdTaskTemplateID(ctx context.Context, id int) (*VqdTask, erro
return &out, nil 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 // GetVqdTask Query a single object
func (c Core) GetVqdTask(ctx context.Context, id int) (*VqdTask, error) { func (c Core) GetVqdTask(ctx context.Context, id int) (*VqdTask, error) {
var out VqdTask var out VqdTask
@ -86,6 +126,16 @@ func (c Core) GetNameVqdTask(ctx context.Context, name string) (*VqdTask, error)
} }
return &out, nil 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 // AddVqdTask Insert into database
func (c Core) AddVqdTask(ctx context.Context, in *AddVqdTaskInput) (*VqdTask, error) { func (c Core) AddVqdTask(ctx context.Context, in *AddVqdTaskInput) (*VqdTask, error) {

View File

@ -1,6 +1,7 @@
package vqdtask package vqdtask
import ( import (
"easyvqd/pkg/vqdcms"
"fmt" "fmt"
"io/fs" "io/fs"
"log/slog" "log/slog"
@ -12,7 +13,6 @@ import (
// 配置参数 // 配置参数
const ( const (
// 要清理的目标目录(请替换为你实际的目录路径) // 要清理的目标目录(请替换为你实际的目录路径)
cleanDir = "/snap"
// 定时任务执行间隔(每天执行一次) // 定时任务执行间隔(每天执行一次)
interval = 24 * time.Hour interval = 24 * time.Hour
// 批量删除大小(避免单次删除过多锁表) // 批量删除大小(避免单次删除过多锁表)
@ -195,10 +195,10 @@ func (c Core) cleanExpiredFiles() error {
expireDays = 1 expireDays = 1
} }
expireTime := time.Now().Add(-expireDays * 24 * time.Hour) 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() dir, _ := os.Getwd()
rootDir := filepath.Join(dir, cleanDir) rootDir := filepath.Join(dir, vqdcms.VQD_IMAGES_DIR)
dateDirs, err := os.ReadDir(rootDir) dateDirs, err := os.ReadDir(rootDir)
if err != nil { if err != nil {
slog.Error("访问根目录路径失败", "path", rootDir, "err", err) slog.Error("访问根目录路径失败", "path", rootDir, "err", err)

View File

@ -2,6 +2,7 @@ package vqdtask
import ( import (
"bufio" "bufio"
"context"
"easyvqd/internal/conf" "easyvqd/internal/conf"
"easyvqd/internal/core/host" "easyvqd/internal/core/host"
"easyvqd/internal/core/vqd" "easyvqd/internal/core/vqd"
@ -19,6 +20,7 @@ type Core struct {
HostCore *host.Core HostCore *host.Core
VqdTaskCore *vqd.Core VqdTaskCore *vqd.Core
Cfg *conf.Bootstrap Cfg *conf.Bootstrap
ResultCb vqdcms.VQDResultCB
} }
var ( var (
@ -31,142 +33,173 @@ func NewCore(HostCore *host.Core, VqdTaskCore *vqd.Core, Cfg *conf.Bootstrap) *C
VqdTaskCore: VqdTaskCore, VqdTaskCore: VqdTaskCore,
Cfg: Cfg, Cfg: Cfg,
} }
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) { core.HostCore.CbIFrame = func(s string, data []byte, codes int) {
//fmt.Println("res", s, codes, len(data))
v, ok := VqdTaskMap.LoadTaskMap(s) v, ok := VqdTaskMap.LoadTaskMap(s)
if ok { if ok {
v.SendData(data, codes) v.SendData(data, VIDEO_CODEC_H265)
} }
//slog.Debug("cb IFrame", "name", s, "codes", codes)
} }
time.AfterFunc(time.Duration(5)*time.Second, func() { 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.InitVqdTask()
core.AddTaskVqd(1, "PVWPQBPIv32UI_01")
}) })
// 启用定时清理任务 // 启用定时清理任务
go core.scheduleCleanTask() go core.scheduleCleanTask()
// 测试
//go core.OpenStartVqd()
// 启用任务管理器 // 启用任务管理器
return core return core
} }
func (c Core) InitVqdTask() { func (c *Core) InitVqdTask() {
err := vqdcms.VQDInit() err := vqdcms.VQDInit()
if err != nil { if err != nil {
slog.Error("vqd cms open", "err", err.Error()) slog.Error("vqd cms open", "err", err.Error())
return return
} }
all, _, err := c.VqdTaskCore.FindVqdTaskAll()
if err == nil {
for _, vqdTask := range all {
c.AddTaskVqd(vqdTask.ID)
time.Sleep(200 * time.Millisecond)
}
}
return return
} }
func (c Core) UnVqdTask() { func (c *Core) UnVqdTask() {
VqdTaskMap.DeleteTaskMapAll()
vqdcms.VQDUnInit() vqdcms.VQDUnInit()
return return
} }
func (c Core) AddTaskVqd(taskId int, chnId string) { func (c *Core) AddTaskVqd(taskId int) error {
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) OpenStartVqd() { task, err := c.VqdTaskCore.GetVqdTask(context.TODO(), taskId)
// if err != nil {
// err := vqdcms.VQDInit() slog.Error("vqd add task find", "err", err.Error())
// if err != nil { return err
// fmt.Println("程序异常", err.Error()) }
// return taskTemplate, err := c.VqdTaskCore.GetIDVqdTaskTemplate(context.TODO(), task.TaskTemplateID)
// } if err != nil {
// dir, _ := os.Getwd() slog.Error("vqd add task find template", "err", err.Error())
// rootPath := filepath.Join(dir, "gbs_buf264") // 你的H.264裸流文件路径 return err
// }
// v := vqdcms.NewVQDHandle(nil, 1) taskPlan, err := c.VqdTaskCore.GetVqdTimeTemplate(context.TODO(), int(task.TimeTemplateID))
// if err != nil {
// para := vqdcms.NewVQDPara() slog.Error("vqd add task find plan", "err", err.Error())
// v.SetVQDConfig(para) return err
// v.Create(para) }
// entries, err := os.ReadDir(rootPath) chnId := task.ChannelID
// if err != nil {
// fmt.Printf("读取目录失败: %v\n", err) para := vqdcms.NewVQDPara(taskTemplate)
// return info := vqdcms.VQDHandleInfo{
// } ChannelID: chnId,
// ChannelName: task.ChannelName,
// fmt.Printf("目录 %s 下的内容:\n", dir) TaskID: task.ID,
// for _, entry := range entries { TaskName: task.Name,
// if entry.IsDir() { TemplateID: taskTemplate.ID,
// fmt.Printf("[目录] %s\n", entry.Name()) TemplateName: taskTemplate.Name,
// } else { PlanID: taskPlan.ID,
// h264Paths := filepath.Join(rootPath, entry.Name()) // 你的H.264裸流文件路径 PlanName: taskPlan.Name,
// fmt.Println(h264Paths) Plans: taskPlan.Plans,
// data, err := os.ReadFile(h264Paths) }
// if err == nil { v := vqdcms.NewVQDHandle(c.ResultCb, c.HostCore, info).Create(para, taskPlan.Plans)
// datap := GetIFramePointer(data)
// width, height, buf, err := v.de.PushDataEx(uintptr(datap.Pointer), datap.Length, VIDEO_CODEC_H264) VqdTaskMap.StoreChildMap(fmt.Sprintf("%s", chnId), v)
// if err == nil { return nil
// v.SendData(buf, width, height) }
// slog.Info("I帧转YUV成功: ", "h264Paths", h264Paths) func (c *Core) UpdateTaskVqd(taskId int) error {
// } else { task, err := c.VqdTaskCore.GetVqdTask(context.TODO(), taskId)
// //slog.Error("I帧转YUV失败: ", "h264Paths", h264Paths ) if err != nil {
// } slog.Error("vqd update task find", "err", err.Error())
// } return err
// } }
// } v, ok := VqdTaskMap.LoadTaskMap(task.ChannelID)
// if ok {
// return taskTemplate, err := c.VqdTaskCore.GetIDVqdTaskTemplate(context.TODO(), task.TaskTemplateID)
//} if err != nil {
// slog.Error("vqd update task find template", "err", err.Error())
//// H264IFrameData 封装I帧数据和指针信息 return err
//type H264IFrameData struct { }
// Data []byte // I帧原始字节数据 taskPlan, err := c.VqdTaskCore.GetVqdTimeTemplate(context.TODO(), int(task.TimeTemplateID))
// Pointer unsafe.Pointer // 指向数据的原始指针 if err != nil {
// Length int // 数据长度(字节数) slog.Error("vqd add task find plan", "err", err.Error())
// IsValid bool // 指针是否有效 return err
//} }
// para := vqdcms.NewVQDPara(taskTemplate)
//// GetIFramePointer 将字节切片转换为原始指针 info := vqdcms.VQDHandleInfo{
//// 注意unsafe包的使用会绕过Go的内存安全检查需谨慎 ChannelID: task.ChannelID,
//func GetIFramePointer(data []byte) *H264IFrameData { ChannelName: task.ChannelName,
// if len(data) == 0 { TaskID: task.ID,
// return &H264IFrameData{ TaskName: task.Name,
// IsValid: false, TemplateID: taskTemplate.ID,
// Length: 0, TemplateName: taskTemplate.Name,
// } PlanID: taskPlan.ID,
// } PlanName: taskPlan.Name,
// Plans: taskPlan.Plans,
// // 方式1直接通过unsafe获取切片底层数组的指针推荐高效 }
// // 切片的底层结构是:指向数组的指针 + 长度 + 容量 errs := v.SetVQDConfig(para, info)
// ptr := unsafe.Pointer(&data[0]) if errs != nil {
// slog.Error("vqd update set config err", "err", errs.Error())
// // 方式2通过reflect获取指针更直观展示切片结构可选 return err
// // sliceHeader := (*reflect.SliceHeader)(unsafe.Pointer(&data)) }
// // ptr := unsafe.Pointer(sliceHeader.Data) VqdTaskMap.StoreChildMap(fmt.Sprintf("%s", task.ChannelID), v)
// }
// return &H264IFrameData{ return nil
// Data: data,
// Pointer: ptr, }
// Length: len(data), func (c *Core) DelTaskVqd(taskId int, chnId string) {
// IsValid: true, v, ok := VqdTaskMap.LoadTaskMap(chnId)
// } if ok {
//} v.Destroy()
}
VqdTaskMap.DeleteTaskMap(chnId)
}
// 测试i帧数据是否可以转为图片文件 // 测试i帧数据是否可以转为图片文件
func CheckIFramesToJpg() { func CheckIFramesToJpg() {

View File

@ -4,3 +4,5 @@ const (
VIDEO_CODEC_H264 = 0x1C VIDEO_CODEC_H264 = 0x1C
VIDEO_CODEC_H265 = 0xAE VIDEO_CODEC_H265 = 0xAE
) )
//status

View File

@ -56,8 +56,8 @@ func setupRouter(r *gin.Engine, uc *Usecase) {
r.GET("/app/metrics/api", web.WrapH(uc.getMetricsAPI)) r.GET("/app/metrics/api", web.WrapH(uc.getMetricsAPI))
//快照 //快照
dir, _ := os.Getwd() dir, _ := os.Getwd()
uploadsDir := filepath.Join(dir, "uploads") uploadsDir := filepath.Join(dir, "snap")
r.Use(statics.Serve("/uploads", statics.LocalFile(uploadsDir, true))) r.Use(statics.Serve("/snap", statics.LocalFile(uploadsDir, true)))
versionapi.Register(r, uc.Version, auth) versionapi.Register(r, uc.Version, auth)
registerConfig(r, ConfigAPI{uc: uc, cfg: uc.Conf}) registerConfig(r, ConfigAPI{uc: uc, cfg: uc.Conf})
@ -77,9 +77,9 @@ func setupRouter(r *gin.Engine, uc *Usecase) {
ctx.Redirect(http.StatusTemporaryRedirect, target) ctx.Redirect(http.StatusTemporaryRedirect, target)
return return
} }
if strings.HasPrefix(p, "/uploads/") { if strings.HasPrefix(p, "/snap/") {
q := ctx.Request.URL.RawQuery q := ctx.Request.URL.RawQuery
target := "/uploads/" target := "/snap/"
if q != "" { if q != "" {
target = target + "?" + q target = target + "?" + q
} }

View File

@ -16,15 +16,18 @@ func (a VqdTaskAPI) findVqdAlarm(c *gin.Context, in *vqd.FindVqdAlarmInput) (any
//row := structs.Map(item) //row := structs.Map(item)
row := make(map[string]interface{}) row := make(map[string]interface{})
row["id"] = item.ID row["id"] = item.ID
row["alarm_name"] = item.AlarmName row["is_deep"] = item.IsDeep
row["alarm_value"] = item.AlarmValue
row["channel_id"] = item.ChannelID row["channel_id"] = item.ChannelID
row["channel_name"] = item.ChannelName row["channel_name"] = item.ChannelName
row["plan_id"] = item.PlanID
row["plan_name"] = item.PlanName
row["task_template_id"] = item.TaskTemplateID row["task_template_id"] = item.TaskTemplateID
row["task_template_name"] = item.TaskTemplateName row["task_template_name"] = item.TaskTemplateName
row["task_id"] = item.TaskID row["task_id"] = item.TaskID
row["task_name"] = item.TaskName row["task_name"] = item.TaskName
row["file_path"] = item.FilePath row["file_path"] = item.FilePath
row["abnormals"] = item.Abnormals
row["default_values"] = item.DefaultValues
row["created_at"] = item.CreatedAt row["created_at"] = item.CreatedAt
row["updated_at"] = item.UpdatedAt 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 := make(map[string]interface{})
row["id"] = item.ID row["id"] = item.ID
row["alarm_name"] = item.AlarmName row["is_deep"] = item.IsDeep
row["alarm_value"] = item.AlarmValue
row["channel_id"] = item.ChannelID row["channel_id"] = item.ChannelID
row["channel_name"] = item.ChannelName row["channel_name"] = item.ChannelName
row["plan_id"] = item.PlanID
row["plan_name"] = item.PlanName
row["task_template_id"] = item.TaskTemplateID row["task_template_id"] = item.TaskTemplateID
row["task_template_name"] = item.TaskTemplateName row["task_template_name"] = item.TaskTemplateName
row["task_id"] = item.TaskID row["task_id"] = item.TaskID
row["task_name"] = item.TaskName row["task_name"] = item.TaskName
row["file_path"] = item.FilePath row["file_path"] = item.FilePath
row["abnormals"] = item.Abnormals
row["default_values"] = item.DefaultValues
row["created_at"] = item.CreatedAt row["created_at"] = item.CreatedAt
row["updated_at"] = item.UpdatedAt row["updated_at"] = item.UpdatedAt

View File

@ -82,6 +82,13 @@ func (a VqdTaskAPI) findVqdTask(c *gin.Context, in *vqd.FindVqdTaskInput) (any,
if errs == nil && template != nil { if errs == nil && template != nil {
row["time_template_name"] = timeTemplate.Name 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["created_at"] = item.CreatedAt
row["updated_at"] = item.UpdatedAt row["updated_at"] = item.UpdatedAt
@ -115,6 +122,13 @@ func (a VqdTaskAPI) getVqdTask(c *gin.Context, _ *struct{}) (any, error) {
if errs == nil && template != nil { if errs == nil && template != nil {
row["time_template_name"] = timeTemplate.Name 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["created_at"] = item.CreatedAt
row["updated_at"] = item.UpdatedAt 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 return gin.H{"data": row}, err
} }
func (a VqdTaskAPI) addVqdTask(c *gin.Context, in *vqd.AddVqdTaskInput) (any, error) { 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 { if err != nil {
return nil, reason.ErrServer.SetMsg(fmt.Sprintf(`add vqdcms err [%s]`, err.Error())) 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 return gin.H{"data": "OK!"}, err
} }
func (a VqdTaskAPI) editVqdTask(c *gin.Context, in *vqd.EditVqdTaskInput) (any, error) { func (a VqdTaskAPI) editVqdTask(c *gin.Context, in *vqd.EditVqdTaskInput) (any, error) {
ID, _ := strconv.Atoi(c.Param("id")) 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 { if err != nil {
return nil, reason.ErrServer.SetMsg(fmt.Sprintf(`find vqdcms [%d] err [%s]`, ID, err.Error())) return nil, reason.ErrServer.SetMsg(fmt.Sprintf(`find vqdcms [%d] err [%s]`, ID, err.Error()))
} }
if task.ChannelID != in.ChannelID {
_, err = a.core.EditVqdTask(c.Request.Context(), in, ID) 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 { if err != nil {
return nil, reason.ErrServer.SetMsg(fmt.Sprintf(`edit vqdcms err [%s]`, err.Error())) 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 return gin.H{"data": "OK!"}, err
} }
func (a VqdTaskAPI) delVqdTask(c *gin.Context, _ *struct{}) (any, error) { func (a VqdTaskAPI) delVqdTask(c *gin.Context, _ *struct{}) (any, error) {
ID, _ := strconv.Atoi(c.Param("id")) 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 { if err != nil {
return nil, reason.ErrServer.SetMsg(fmt.Sprintf(`del vqdcms [%d] err [%s]`, ID, err.Error())) 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 return gin.H{"data": "OK!"}, err
} }

View File

@ -69,17 +69,27 @@ func (a VqdTaskAPI) getVqdTaskTemplate(c *gin.Context, _ *struct{}) (any, error)
return gin.H{"data": row}, err return gin.H{"data": row}, err
} }
func (a VqdTaskAPI) addVqdTaskTemplate(c *gin.Context, in *vqd.AddVqdTaskTemplateInput) (any, error) { 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 { if err != nil {
return nil, reason.ErrServer.SetMsg(fmt.Sprintf(`add vqdcms err [%s]`, err.Error())) 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 return gin.H{"data": "OK!"}, err
} }
func (a VqdTaskAPI) editVqdTaskTemplate(c *gin.Context, in *vqd.EditVqdTaskTemplateInput) (any, error) { func (a VqdTaskAPI) editVqdTaskTemplate(c *gin.Context, in *vqd.EditVqdTaskTemplateInput) (any, error) {
ID, _ := strconv.Atoi(c.Param("id")) 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 { if err != nil {
return nil, reason.ErrServer.SetMsg(fmt.Sprintf(`find vqdcms [%d] err [%s]`, ID, err.Error())) 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 { if err != nil {
return nil, reason.ErrServer.SetMsg(fmt.Sprintf(`edit vqdcms err [%s]`, err.Error())) 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 return gin.H{"data": "OK!"}, err
} }

View File

@ -49,17 +49,27 @@ func (a VqdTaskAPI) getVqdTimeTemplate(c *gin.Context, _ *struct{}) (any, error)
return gin.H{"data": row}, err return gin.H{"data": row}, err
} }
func (a VqdTaskAPI) addVqdTimeTemplate(c *gin.Context, in *vqd.AddVqdTimeTemplateInput) (any, error) { 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 { if err != nil {
return nil, reason.ErrServer.SetMsg(fmt.Sprintf(`add vqdcms err [%s]`, err.Error())) 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 return gin.H{"data": "OK!"}, err
} }
func (a VqdTaskAPI) editVqdTimeTemplate(c *gin.Context, in *vqd.EditVqdTimeTemplateInput) (any, error) { func (a VqdTaskAPI) editVqdTimeTemplate(c *gin.Context, in *vqd.EditVqdTimeTemplateInput) (any, error) {
ID, _ := strconv.Atoi(c.Param("id")) 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 { if err != nil {
return nil, reason.ErrServer.SetMsg(fmt.Sprintf(`find vqdcms [%d] err [%s]`, ID, err.Error())) 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 { if err != nil {
return nil, reason.ErrServer.SetMsg(fmt.Sprintf(`edit vqdcms err [%s]`, err.Error())) 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 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())) return nil, reason.ErrServer.SetMsg(fmt.Sprintf(`find vqdcms [%d] err [%s]`, ID, err.Error()))
} }
if info.IsDefault { 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) _, err = a.core.DelVqdTimeTemplate(c.Request.Context(), ID)
if err != nil { if err != nil {
return nil, reason.ErrServer.SetMsg(fmt.Sprintf(`del vqdcms [%d] err [%s]`, ID, err.Error())) return nil, reason.ErrServer.SetMsg(fmt.Sprintf(`del vqdcms [%d] err [%s]`, ID, err.Error()))

View File

@ -20,7 +20,6 @@ import "C"
import ( import (
"fmt" "fmt"
"log/slog" "log/slog"
"os"
"sync" "sync"
"unsafe" "unsafe"
) )
@ -29,14 +28,6 @@ type VideoDecoder struct {
handle C.EZ_DECODER_HANDLE handle C.EZ_DECODER_HANDLE
sync.RWMutex sync.RWMutex
IsStart bool IsStart bool
file *os.File
}
func (v *VideoDecoder) WriteFile(data string) {
if v.file != nil {
v.file.WriteString(data)
}
} }
func (v *VideoDecoder) Create() error { func (v *VideoDecoder) Create() error {
@ -45,16 +36,6 @@ func (v *VideoDecoder) Create() error {
if v.IsStart { if v.IsStart {
return nil 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)) ret := int(C.ez_decoder_create(&v.handle))
v.IsStart = true v.IsStart = true
return IsReturnErrorf(ret, "ez_decoder_create") return IsReturnErrorf(ret, "ez_decoder_create")
@ -67,43 +48,29 @@ func (v *VideoDecoder) Destroy() error {
return nil return nil
} }
v.IsStart = false 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) slog.Info("====>>ez_decoder_destroy", "v", v)
ret := int(C.ez_decoder_destroy(&v.handle)) ret := int(C.ez_decoder_destroy(&v.handle))
return IsReturnErrorf(ret, "ez_decoder_destroy") 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() v.Lock()
defer v.Unlock() defer v.Unlock()
if !v.IsStart { if !v.IsStart {
return 0, 0, nil, fmt.Errorf("decoder fail") return 0, 0, nil, fmt.Errorf("decoder fail")
} }
if v.file != nil { cBytes := C.CBytes(buf)
v.file.WriteString(fmt.Sprintf("[start] function:PushData;buf len:%d; codec:%x\r\n", size, codec)) defer C.free(unsafe.Pointer(cBytes)) // 必须手动释放
}
info := &C.EZDecoderInfo{} info := &C.EZDecoderInfo{}
var out_data *C.uchar var out_data *C.uchar
//llog.Info("=======================>>>>>>:%v:%d", buf, size) ret := int(C.ez_decoder_push_data(v.handle, (*C.uchar)(cBytes), C.int(len(buf)), C.int(codec), &out_data, info))
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))
}
//return 0, 0, nil, fmt.Errorf("test") // 252 //return 0, 0, nil, fmt.Errorf("test") // 252
if ret == 0 { if ret == 0 {
data_size := int(info.data_size) data_size := int(info.data_size)
_w := int(info.width) _w := int(info.width)
_h := int(info.height) _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) yuv := C.GoBytes(unsafe.Pointer(out_data), info.data_size)
_data := make([]byte, data_size) _data := make([]byte, data_size)
copy(_data, yuv) copy(_data, yuv)
//llog.Info("=======================>>>>>>22222")
return _w, _h, _data, nil return _w, _h, _data, nil
//_data := make([]byte, data_size) //_data := make([]byte, data_size)

View File

@ -9,7 +9,7 @@ type VqdTaskMap struct {
sync.RWMutex sync.RWMutex
} }
// 读出相应Key的Map // 读出相应Key的Map
func (m *VqdTaskMap) LoadTaskMap(key string) (value *VQDHandle, ok bool) { func (m *VqdTaskMap) LoadTaskMap(key string) (value *VQDHandle, ok bool) {
m.RLock() m.RLock()
defer m.RUnlock() defer m.RUnlock()
@ -17,16 +17,25 @@ func (m *VqdTaskMap) LoadTaskMap(key string) (value *VQDHandle, ok bool) {
return return
} }
// 删除相应Key的Map // 删除相应Key的Map
func (m *VqdTaskMap) DeleteTaskMap(key string) { func (m *VqdTaskMap) DeleteTaskMap(key string) {
m.Lock() m.Lock()
defer m.Unlock() defer m.Unlock()
delete(m.M, key) delete(m.M, key)
} }
// 增加或修改相应Key的Map // 增加或修改相应Key的Map
func (m *VqdTaskMap) StoreChildMap(key string, value *VQDHandle) { func (m *VqdTaskMap) StoreChildMap(key string, value *VQDHandle) {
m.Lock() m.Lock()
defer m.Unlock() defer m.Unlock()
m.M[key] = value m.M[key] = value
} }
// 删除全部的Map
func (m *VqdTaskMap) DeleteTaskMapAll() {
m.RLock()
defer m.RUnlock()
for _, handle := range m.M {
handle.Destroy()
}
}

View File

@ -2,10 +2,13 @@ package vqdcms
import ( import (
"database/sql/driver" "database/sql/driver"
"easyvqd/internal/core/vqd"
"encoding/json" "encoding/json"
"time" "time"
) )
const VQD_IMAGES_DIR = "snap"
type Abnormal struct { type Abnormal struct {
Value float32 `json:"value"` Value float32 `json:"value"`
Name string `json:"name"` Name string `json:"name"`
@ -43,11 +46,13 @@ func (r defaultValue) Value() (driver.Value, error) {
type AbnormalModel struct { type AbnormalModel struct {
ID int `gorm:"primary_key" json:"id"` ID int `gorm:"primary_key" json:"id"`
TaskName string `json:"task_name"`
ChannelID string `json:"channel_id"` ChannelID string `json:"channel_id"`
ChannelName string `json:"channel_name"` ChannelName string `json:"channel_name"`
AlgoID int `json:"algo_id"` PlanID int `json:"plan_id"`
AlgoName string `json:"algo_name"` PlanName string `json:"plan_name"`
TaskName string `json:"task_name"` TemplateID int `json:"template_id"`
TemplateName string `json:"template_name"`
FilePath string `json:"file_path"` FilePath string `json:"file_path"`
IsDeep bool `json:"is_deep"` IsDeep bool `json:"is_deep"`
Abnormals abnormal `gorm:"type:json" json:"abnormals"` Abnormals abnormal `gorm:"type:json" json:"abnormals"`
@ -267,58 +272,95 @@ type VQDPara struct {
FlowerPara VQDFlowerPara /* 花屏检测 */ FlowerPara VQDFlowerPara /* 花屏检测 */
} }
// config cvrdo.VQDAlgoConfig // config
func NewVQDPara() VQDPara { 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{ return VQDPara{
VecFrmNum: 2, VecFrmNum: int(tem.VqdConfig.FrmNum),
UseDeepLearning: true, UseDeepLearning: tem.VqdConfig.IsDeepLearn,
EnableFunc: GetAlgoEnable([]string{"vqd_config", "vqd_lgt_dark", "vqd_blue", "vqd_clarity", "vqd_shark", "vqd_freeze", EnableFunc: EnableFunc,
"vqd_color", "vqd_occlusion", "vqd_noise", "vqd_contrast", "vqd_mosaic", "vqd_flower"}),
ColorPara: VQDColorPara{ ColorPara: VQDColorPara{
ColorThr: 0.3, ColorThr: tem.VqdColor.ColorThr,
ColorAbnNumRatio: 0.6, ColorAbnNumRatio: tem.VqdColor.ColorAbnNumRatio,
}, },
LgtDarkPara: VQDLgtDarkPara{ LgtDarkPara: VQDLgtDarkPara{
LightThr: 0.3, LightThr: tem.VqdLgtDark.LgtThr,
DarkThr: 0.4, DarkThr: tem.VqdLgtDark.DarkThr,
LgtDarkAbnNumRatio: 0.6, LgtDarkAbnNumRatio: tem.VqdLgtDark.LgtDarkAbnNumRatio,
}, },
ClarityPara: VQDClarityPara{ ClarityPara: VQDClarityPara{
ClarityThr: 0.5, ClarityThr: tem.VqdClarity.ClarityThr,
ClarityAbnNumRatio: 0.6, ClarityAbnNumRatio: tem.VqdClarity.ClarityAbnNumRatio,
}, },
NoisePara: VQDNoisePara{ NoisePara: VQDNoisePara{
NoiseThr: 0.3, NoiseThr: tem.VqdNoise.NoiseThr,
NoiseAbnNumRatio: 0.6, NoiseAbnNumRatio: tem.VqdNoise.NoiseAbnNumRatio,
}, },
ContrastPara: VQDContrastPara{ ContrastPara: VQDContrastPara{
CtraLowThr: 0.3, CtraLowThr: tem.VqdContrast.CtraLowThr,
CtraHighThr: 0.8, CtraHighThr: tem.VqdContrast.CtraHighThr,
CtraAbnNumRatio: 0.6, CtraAbnNumRatio: tem.VqdContrast.CtraAbnNumRatio,
}, },
OcclusionPara: VQDOcclusionPara{ OcclusionPara: VQDOcclusionPara{
OcclusionThr: 0.1, OcclusionThr: tem.VqdOcclusion.OcclusionThr,
OcclusionAbnNumRatio: 0.6, OcclusionAbnNumRatio: tem.VqdOcclusion.OcclusionAbnNumRatio,
}, },
BluePara: VQDBluePara{ BluePara: VQDBluePara{
BlueThr: 0.3, BlueThr: tem.VqdBlue.BlueThr,
BlueAbnNumRatio: 0.6, BlueAbnNumRatio: tem.VqdBlue.BlueAbnNumRatio,
}, },
SharkPara: VQDSharkPara{ SharkPara: VQDSharkPara{
SharkThr: 0.2, SharkThr: tem.VqdShark.SharkThr,
SharkAbnNumRatio: 0.2, SharkAbnNumRatio: tem.VqdShark.SharkAbnNumRatio,
}, },
FreezePara: VQDFreezePara{ FreezePara: VQDFreezePara{
FreezeThr: 0.999, FreezeThr: tem.VqdFreeze.FreezeThr,
FreezeAbnNumRatio: 0.99, FreezeAbnNumRatio: tem.VqdFreeze.FreezeAbnNumRatio,
}, },
MosaicPara: VQDMosaicPara{ MosaicPara: VQDMosaicPara{
MosaicThr: 0.3, MosaicThr: tem.VqdMosaic.MosaicThr,
MosaicAbnNumRatio: 0.6, MosaicAbnNumRatio: tem.VqdMosaic.MosaicAbnNumRatio,
}, },
FlowerPara: VQDFlowerPara{ FlowerPara: VQDFlowerPara{
FlowerThr: 0.3, FlowerThr: tem.VqdFlower.FlowerThr,
FlowerAbnNumRatio: 0.6, FlowerAbnNumRatio: tem.VqdFlower.FlowerAbnNumRatio,
}, },
} }
} }

View File

@ -1,5 +1,12 @@
package vqdcms package vqdcms
type VqdTaskStatus int
const (
TaskStatusStopped VqdTaskStatus = iota
TaskStatusRunning
TaskStatusFailed
)
const NXU_VQD_DISABLE_ALL = 0x00000000 /* 所有功能关闭 */ const NXU_VQD_DISABLE_ALL = 0x00000000 /* 所有功能关闭 */
const NXU_VQD_ENABLE_COLOR = 0x00000001 /* 偏色检测 */ const NXU_VQD_ENABLE_COLOR = 0x00000001 /* 偏色检测 */
const NXU_VQD_ENABLE_LGTDARK = 0x00000002 /* 过亮过暗检测 */ const NXU_VQD_ENABLE_LGTDARK = 0x00000002 /* 过亮过暗检测 */

View File

@ -7,9 +7,10 @@ package vqdcms
*/ */
import "C" import "C"
import ( import (
"context"
"easyvqd/internal/core/host"
"easyvqd/pkg/decoder" "easyvqd/pkg/decoder"
"fmt" "fmt"
"github.com/shirou/gopsutil/v4/mem"
"log/slog" "log/slog"
"os" "os"
"path/filepath" "path/filepath"
@ -29,20 +30,33 @@ type VideoInfoVQD struct {
Params VQDPara Params VQDPara
IsCreateSuccess bool 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 { type VQDHandle struct {
running uint32 running uint32
TaskID int // 标识ID ID int // 标识ID
ChnID string // 通道ID info VQDHandleInfo
Plans string // 任务计划 plansLock sync.RWMutex
playTicker *Scheduler
ErrorMsg string
data chan ChanData data chan ChanData
dataLock sync.RWMutex dataLock sync.RWMutex
cb VQDResultCB cb VQDResultCB
handle *VideoInfoVQD handle *VideoInfoVQD
name string // 算法名称 name string // 算法名称
file *os.File
decoder *decoder.VideoDecoder decoder *decoder.VideoDecoder
fileLock sync.RWMutex fileLock sync.RWMutex
hostCore *host.Core
Status VqdTaskStatus
} }
type VQDResultCB func(AbnormalModel) type VQDResultCB func(AbnormalModel)
@ -54,53 +68,91 @@ type ChanData struct {
now time.Time 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 return false
} }
func NewVQDHandle(cb VQDResultCB, taskId int, chnId string) *VQDHandle { func NewVQDHandle(cb VQDResultCB, hostCore *host.Core, info VQDHandleInfo) *VQDHandle {
v := &VQDHandle{ v := &VQDHandle{
running: 0, running: 0,
decoder: &decoder.VideoDecoder{}, decoder: &decoder.VideoDecoder{},
ChnID: chnId, info: info,
TaskID: taskId, ID: info.TaskID,
data: make(chan ChanData, MAX_STREAM_CHAN_NUM), data: make(chan ChanData, MAX_STREAM_CHAN_NUM),
cb: cb, cb: cb,
handle: &VideoInfoVQD{}, handle: &VideoInfoVQD{},
playTicker: NewScheduler(),
hostCore: hostCore,
Status: TaskStatusStopped,
} }
err := v.decoder.Create() err := v.decoder.Create()
if err != nil { if err != nil {
slog.Error("decoder Create ", "taskId", chnId, "err", err) slog.Error("decoder Create ", "taskId", info.ChannelID, "err", err)
} }
v.StartPlay()
return v 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) return v.handle.Config(params, params.EnableFunc, 10)
} }
func (v *VQDHandle) GetHandle() *VideoInfoVQD { func (v *VQDHandle) GetHandle() *VideoInfoVQD {
return v.handle 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 { if atomic.LoadUint32(&v.running) == 1 {
return v 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) err := v.handle.Create(params, params.EnableFunc, 10)
if err != nil { 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 return v
} }
atomic.StoreUint32(&v.running, 1) atomic.StoreUint32(&v.running, 1)
@ -109,16 +161,17 @@ func (v *VQDHandle) Create(params VQDPara) *VQDHandle {
} }
func (v *VQDHandle) Destroy() { 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 { if v.data != nil {
close(v.data) 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 v.data = nil
if atomic.LoadUint32(&v.running) == 1 { if atomic.LoadUint32(&v.running) == 1 {
atomic.StoreUint32(&v.running, 0) atomic.StoreUint32(&v.running, 0)
@ -133,10 +186,10 @@ func (v *VQDHandle) RunFrame() {
print(fmt.Sprintf("%s\n", string(debug.Stack()))) 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) cvqdImgsDir = filepath.ToSlash(cvqdImgsDir)
if err := os.MkdirAll(cvqdImgsDir, os.ModePerm); err != nil { 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 hyper := 0
@ -149,7 +202,7 @@ func (v *VQDHandle) RunFrame() {
return return
} }
if len(v.data) >= (MAX_STREAM_CHAN_NUM - 6) { 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 hyper = MAX_STREAM_CHAN_NUM / 10
} }
if hyper > 0 { if hyper > 0 {
@ -157,16 +210,11 @@ func (v *VQDHandle) RunFrame() {
break break
} }
cdata = data cdata = data
//if !IsCurTimeInRecordPlan(v.analysisTime, cdata.now) { if !IsCurTimeInRecordPlan(v.info.Plans, cdata.now) {
// continue 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()
} }
now := time.Now().UnixMilli() 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) fpath = filepath.ToSlash(fpath)
result := VQDResult{} result := VQDResult{}
ret := v.handle.Frame(cdata.data, cdata.w, cdata.h, index, fpath, &result) ret := v.handle.Frame(cdata.data, cdata.w, cdata.h, index, fpath, &result)
@ -174,8 +222,15 @@ func (v *VQDHandle) RunFrame() {
index = 0 index = 0
if value, b := v.parseVQD(result); b { if value, b := v.parseVQD(result); b {
value.FilePath = strings.TrimPrefix(filepath.ToSlash(fpath), filepath.ToSlash(CWD())) value.FilePath = strings.TrimPrefix(filepath.ToSlash(fpath), filepath.ToSlash(CWD()))
value.ChannelID = v.ChnID value.TaskName = v.info.TaskName
value.AlgoName = v.name 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 { if v.cb != nil {
v.cb(value) v.cb(value)
} else { } 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(fp)
//C.free(yuvBuf) //C.free(yuvBuf)
index++ index++
@ -368,14 +411,10 @@ func (v *VQDHandle) parseVQD(result VQDResult) (AbnormalModel, bool) {
return abnormals, isabnormal return abnormals, isabnormal
} }
func (v *VQDHandle) SendData(buf []byte, _codec int) { func (v *VQDHandle) SendData(buf []byte, _codec int) {
dataP := GetIFramePointer(buf)
if dataP == nil || !dataP.IsValid { w, h, data, err := v.decoder.PushDataEx(buf, _codec)
slog.Error("I帧转指针失败: ", "TaskID", v.TaskID)
return
}
w, h, data, err := v.decoder.PushDataEx(uintptr(dataP.Pointer), dataP.Length, _codec)
if err != nil { if err != nil {
slog.Error("I帧转YUV失败: ", "TaskID", v.TaskID, "err", err) slog.Error("I帧转YUV失败: ", "TaskID", v.info.TaskID, "err", err)
return return
} }
if len(data) > 0 && v.data != nil { if len(data) > 0 && v.data != nil {
@ -388,9 +427,6 @@ func (v *VQDHandle) SendData(buf []byte, _codec int) {
h: h, h: h,
now: now, now: now,
} }
//v.dataLock.Lock()
//v.data = append(v.data, d)
//v.dataLock.Unlock()
v.data <- d v.data <- d
} }
} }

56
pkg/vqdcms/worker.go Normal file
View File

@ -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 // 防止重复关闭
}
}

View File

@ -36,6 +36,7 @@ const AddVqdTask = forwardRef<AddVqdTaskRef, AddVqdTaskProps>(
channel_id: task.channel_id, channel_id: task.channel_id,
channel_name: task.channel_name, channel_name: task.channel_name,
task_template_id: task.task_template_id, task_template_id: task.task_template_id,
time_template_id: task.time_template_id,
task_template_name: task.task_template_name, task_template_name: task.task_template_name,
enable: task.enable, enable: task.enable,
}; };

View File

@ -114,7 +114,7 @@ const AddVqdTaskTemplate = forwardRef<AddVqdTaskTemplateRef, AddVqdTaskTemplateP
const { mutate: createMutate, isPending: creating } = useMutation({ const { mutate: createMutate, isPending: creating } = useMutation({
mutationFn: CreateVqdTaskTemplate, mutationFn: CreateVqdTaskTemplate,
onSuccess: () => { onSuccess: () => {
message.success("创建任务成功"); message.success("创建成功");
handleClose(); handleClose();
onSuccess?.(); onSuccess?.();
}, },
@ -124,7 +124,7 @@ const AddVqdTaskTemplate = forwardRef<AddVqdTaskTemplateRef, AddVqdTaskTemplateP
const { mutate: updateMutate, isPending: updating } = useMutation({ const { mutate: updateMutate, isPending: updating } = useMutation({
mutationFn: UpdateVqdTaskTemplate, mutationFn: UpdateVqdTaskTemplate,
onSuccess: () => { onSuccess: () => {
message.success("更新任务成功"); message.success("更新成功");
handleClose(); handleClose();
onSuccess?.(); onSuccess?.();
}, },

View File

@ -167,7 +167,7 @@ const AddVqdTimeTemplate = forwardRef<AddVqdTimeTemplateRef, AddVqdTimeTemplateP
const { mutate: createMutate, isPending: creating } = useMutation({ const { mutate: createMutate, isPending: creating } = useMutation({
mutationFn: CreateVqdTimeTemplate, mutationFn: CreateVqdTimeTemplate,
onSuccess: () => { onSuccess: () => {
message.success("创建任务成功"); message.success("创建成功");
handleClose(); handleClose();
onSuccess?.(); onSuccess?.();
}, },
@ -177,7 +177,7 @@ const AddVqdTimeTemplate = forwardRef<AddVqdTimeTemplateRef, AddVqdTimeTemplateP
const { mutate: updateMutate, isPending: updating } = useMutation({ const { mutate: updateMutate, isPending: updating } = useMutation({
mutationFn: UpdateVqdTimeTemplate, mutationFn: UpdateVqdTimeTemplate,
onSuccess: () => { onSuccess: () => {
message.success("更新任务成功"); message.success("更新成功");
handleClose(); handleClose();
onSuccess?.(); onSuccess?.();
}, },

View File

@ -1,6 +1,6 @@
import { useRef, useState, useMemo } from "react"; import { useRef, useState, useMemo } from "react";
import { Table, Button, Space, Popconfirm, Flex, message, Tooltip, Select, Row, Col, Empty, Pagination, Tag } from "antd"; import { Table, Button, Space, Popconfirm, Flex, message, Tooltip, Select, Row, Col, Empty, Pagination, Tag, Popover } from "antd";
import { EditOutlined, DeleteOutlined, PlusOutlined } from "@ant-design/icons"; import { EditOutlined, DeleteOutlined, InfoCircleOutlined } from "@ant-design/icons";
import { useQuery, useMutation } from "@tanstack/react-query"; import { useQuery, useMutation } from "@tanstack/react-query";
import { GetVqdAlarm, DeleteVqdAlarm, DeleteVqdAlarmAll } from "../api/vqdalarm"; import { GetVqdAlarm, DeleteVqdAlarm, DeleteVqdAlarmAll } from "../api/vqdalarm";
import type { VqdAlarmItem } from "../types/vqdalarm"; import type { VqdAlarmItem } from "../types/vqdalarm";
@ -233,7 +233,7 @@ export default function VqdAlarmPage() {
loading={moonLoading} loading={moonLoading}
/> */} /> */}
<div className="w-[150px] ml-[20px]"> <div className="w-[150px] ml-[20px]">
<Select {/* <Select
defaultValue={0} defaultValue={0}
className="w-[100%] mr-2" className="w-[100%] mr-2"
placeholder="选择类型" placeholder="选择类型"
@ -255,7 +255,7 @@ export default function VqdAlarmPage() {
</span> </span>
</Space> </Space>
)} )}
/> /> */}
<Filter <Filter
searchLoading={isLoading} searchLoading={isLoading}
@ -274,12 +274,29 @@ export default function VqdAlarmPage() {
<AlarmSnap filePath={item.file_path} /> <AlarmSnap filePath={item.file_path} />
<div className="pl-3 pr-2 pb-1"> <div className="pl-3 pr-2 pb-1">
<Flex justify="space-between" align="center"> <Flex align="center" wrap gap="small">
<Tag bordered={false} color="#87d068" className="m-0 mt-2">{item.alarm_name}</Tag> {
<Space className="pr-2"> :{item.channel_name||"2222"}</Space> item.abnormals.map((item) => {
return (<>
<Tag bordered={false} color="#87d068" className="m-0 mt-2">{item.name}</Tag>
</>)
})
}
</Flex> </Flex>
<div>
<Space className="pt-2"> :{item.task_name}</Space>
</div>
{/* <Space className="pt-2"> 通道:{item.channel_name || item.channel_id}</Space> */}
<Flex justify="space-between" align="center"> <Flex justify="space-between" align="center">
<p className="m-0"> {item.created_at}</p> <p className="m-0"> {item.created_at}</p>
<div>
<Popover content={(<>
<div>: {item.channel_name || item.channel_id}</div>
<div>: {item.plan_name}</div>
<div className="pb-2">: {item.task_template_name} </div>
</>)} title="详情">
<Button type="text" icon={<InfoCircleOutlined />}></Button>
</Popover>
<Tooltip title="删除"> <Tooltip title="删除">
<Popconfirm <Popconfirm
title="删除告警" title="删除告警"
@ -292,6 +309,8 @@ export default function VqdAlarmPage() {
<Button type="text" danger icon={<DeleteOutlined />}></Button> <Button type="text" danger icon={<DeleteOutlined />}></Button>
</Popconfirm> </Popconfirm>
</Tooltip> </Tooltip>
</div>
</Flex> </Flex>
</div> </div>
</div> </div>

View File

@ -1,5 +1,5 @@
import { useRef, useState, useMemo } from "react"; import { useRef, useState, useMemo } from "react";
import { Table, Button, Space, Popconfirm, Flex, message, Tooltip, Switch } from "antd"; import { Table, Button, Space, Popconfirm, Flex, message, Tooltip, Switch,Popover,Tag } from "antd";
import { EditOutlined, DeleteOutlined, PlusOutlined } from "@ant-design/icons"; import { EditOutlined, DeleteOutlined, PlusOutlined } from "@ant-design/icons";
import { useQuery, useMutation } from "@tanstack/react-query"; import { useQuery, useMutation } from "@tanstack/react-query";
import { GetVqdTask, DeleteVqdTask, UpdateVqdTask } from "../api/vqdtask"; import { GetVqdTask, DeleteVqdTask, UpdateVqdTask } from "../api/vqdtask";
@ -160,6 +160,26 @@ export default function VqdTaskPage() {
</Space> </Space>
), ),
}, },
{
title: "拉流状态",
dataIndex: "status",
align: "center",
render: (text, record) => (
<Space>
{text == 0 && <Tag color="#ccc"></Tag>}
{text == 1 && <Tag color="#87d068"></Tag>}
{text == 2 &&
<Popover content={
<>
{record.error_msg}
</>
} title="异常详情">
<Tag color="#f50"></Tag>
</Popover>
}
</Space>
),
},
{ {
title: "创建日期", title: "创建日期",
dataIndex: "created_at", dataIndex: "created_at",

View File

@ -356,7 +356,9 @@
.overflow-y-auto { .overflow-y-auto {
overflow-y: auto; overflow-y: auto;
} }
.overflow-hidden {
overflow: hidden;
}
.truncate { .truncate {
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
@ -474,6 +476,10 @@
padding-bottom: 0.75rem; padding-bottom: 0.75rem;
} }
.pt-2 {
padding-top: 0.5rem;
}
.pl-2 { .pl-2 {
padding-left: 0.5rem; padding-left: 0.5rem;
} }

View File

@ -16,22 +16,34 @@ export type VqdAlarmRes = {
*/ */
total: number; total: number;
}; };
export type Abnormals = {
value: number;
name: string;
}
export type DefaultValues = {
thr1: number;
name1: string;
thr2: number;
name2: string;
ratio: number;
}
/** /**
* *
*/ */
export type VqdAlarmItem = { export type VqdAlarmItem = {
id: number; id: number;
alarm_name: string; is_deep: boolean;
alarm_value: string;
channel_id: string; channel_id: string;
channel_name: string; channel_name: string;
plan_id: number;
plan_name: string;
task_template_id: number; task_template_id: number;
task_template_name: string; task_template_name: string;
task_id: number; task_id: number;
task_name: string; task_name: string;
file_path: string; file_path: string;
abnormals: Abnormals[];
default_values: DefaultValues[];
created_at?: string; created_at?: string;
updated_at?: string; updated_at?: string;

View File

@ -26,6 +26,8 @@ export type VqdTaskItem = {
name: string; name: string;
channel_id: string; channel_id: string;
channel_name: string; channel_name: string;
error_msg: string;
status: number;
task_template_id: number; task_template_id: number;
time_template_id: number; time_template_id: number;
task_template_name: string; task_template_name: string;
@ -103,6 +105,8 @@ export type VqdTaskDetailRes = {
name: string; name: string;
channel_id: string; channel_id: string;
channel_name: string; channel_name: string;
error_msg: string;
status: number;
task_template_id: number; task_template_id: number;
time_template_id: number; time_template_id: number;
task_template_name: string; task_template_name: string;

View File

@ -21,7 +21,7 @@ export default defineConfig(({ mode }) => {
// 可选:重写路径 // 可选:重写路径
// rewrite: (path) => path.replace(/^\/api/, '') // rewrite: (path) => path.replace(/^\/api/, '')
}, },
'/uploads': { '/snap': {
target: 'http://127.0.0.1:8089', target: 'http://127.0.0.1:8089',
changeOrigin: true, changeOrigin: true,
secure: false, secure: false,