调整告警图片展示

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
*.test
snap/*
*buf264/*
# Logs
logs
*.log

View File

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

View File

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

View File

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

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 {
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

View File

@ -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())

View File

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

View File

@ -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) {

View File

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

View File

@ -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() {

View File

@ -4,3 +4,5 @@ const (
VIDEO_CODEC_H264 = 0x1C
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))
//快照
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
}

View File

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

View File

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

View File

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

View File

@ -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()))

View File

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

View File

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

View File

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

View File

@ -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 /* 过亮过暗检测 */

View File

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

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_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,
};

View File

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

View File

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

View File

@ -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}
/> */}
<div className="w-[150px] ml-[20px]">
<Select
{/* <Select
defaultValue={0}
className="w-[100%] mr-2"
placeholder="选择类型"
@ -255,7 +255,7 @@ export default function VqdAlarmPage() {
</span>
</Space>
)}
/>
/> */}
<Filter
searchLoading={isLoading}
@ -274,24 +274,43 @@ export default function VqdAlarmPage() {
<AlarmSnap filePath={item.file_path} />
<div className="pl-3 pr-2 pb-1">
<Flex justify="space-between" align="center">
<Tag bordered={false} color="#87d068" className="m-0 mt-2">{item.alarm_name}</Tag>
<Space className="pr-2"> :{item.channel_name||"2222"}</Space>
<Flex align="center" wrap gap="small">
{
item.abnormals.map((item) => {
return (<>
<Tag bordered={false} color="#87d068" className="m-0 mt-2">{item.name}</Tag>
</>)
})
}
</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">
<p className="m-0"> {item.created_at}</p>
<Tooltip title="删除">
<Popconfirm
title="删除告警"
description="确定要删除此告警吗?"
onConfirm={() => {
deleteMutation(item.id)
}}
>
{/* loading={isDelLoading} */}
<Button type="text" danger icon={<DeleteOutlined />}></Button>
</Popconfirm>
</Tooltip>
<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="删除">
<Popconfirm
title="删除告警"
description="确定要删除此告警吗?"
onConfirm={() => {
deleteMutation(item.id)
}}
>
{/* loading={isDelLoading} */}
<Button type="text" danger icon={<DeleteOutlined />}></Button>
</Popconfirm>
</Tooltip>
</div>
</Flex>
</div>
</div>

View File

@ -1,5 +1,5 @@
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 { useQuery, useMutation } from "@tanstack/react-query";
import { GetVqdTask, DeleteVqdTask, UpdateVqdTask } from "../api/vqdtask";
@ -160,6 +160,26 @@ export default function VqdTaskPage() {
</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: "创建日期",
dataIndex: "created_at",

View File

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

View File

@ -16,22 +16,34 @@ export type VqdAlarmRes = {
*/
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 = {
id: number;
alarm_name: string;
alarm_value: string;
is_deep: boolean;
channel_id: string;
channel_name: string;
plan_id: number;
plan_name: string;
task_template_id: number;
task_template_name: string;
task_id: number;
task_name: string;
file_path: string;
abnormals: Abnormals[];
default_values: DefaultValues[];
created_at?: string;
updated_at?: string;

View File

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

View File

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