package vqdcms /* #include #include #include */ import "C" import ( "context" "easyvqd/internal/core/host" "easyvqd/pkg/decoder" "fmt" "log/slog" "os" "path/filepath" "runtime/debug" "strings" "sync" "sync/atomic" "time" "unsafe" ) const MAX_STREAM_CHAN_NUM = 256 type VideoInfoVQD struct { mu sync.RWMutex VQDHandle uintptr 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 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) type ChanData struct { data []byte w int h int now time.Time } // 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, hostCore *host.Core, info VQDHandleInfo) *VQDHandle { v := &VQDHandle{ 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", info.ChannelID, "err", err) } v.StartPlay() return v } 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) 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: 40, }) 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 } err := v.handle.Create(params, params.EnableFunc, 10) if err != nil { slog.Error("vqd create", "taskId", v.info.TaskID, "chnId", v.info.ChannelID, "fail", err) return v } atomic.StoreUint32(&v.running, 1) go v.RunFrame() return v } func (v *VQDHandle) Destroy() { 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) v.handle.Destroy() } } func (v *VQDHandle) RunFrame() { defer func() { if e := recover(); e != nil { print(fmt.Sprintf("RunFrame---%s\n", e)) print(fmt.Sprintf("%s\n", string(debug.Stack()))) } }() 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 { slog.Error("vqd create img dir", "taskId", v.info.TaskID, "chnId", v.info.ChannelID, "err", err) } hyper := 0 index := 0 cdata := ChanData{} for { select { case data, ok := <-v.data: if !ok { return } if len(v.data) >= (MAX_STREAM_CHAN_NUM - 6) { 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 { hyper-- break } cdata = data now := time.Now().UnixMilli() 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) if ret == 0 { index = 0 if value, b := v.parseVQD(result); b { value.FilePath = strings.TrimPrefix(filepath.ToSlash(fpath), filepath.ToSlash(CWD())) 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 { // 保存数据库 //CreateAbnormalModel(&value) } } } //C.free(fp) //C.free(yuvBuf) index++ } } } // VQD_ENABLE_COLOR = "vqd_color" // VQD_ENABLE_LGTDARK = "vqd_lgt_dark" // VQD_ENABLE_CLARITY = "vqd_clarity" // VQD_ENABLE_NOISE = "vqd_noise" // VQD_ENABLE_CONTRAST = "vqd_contrast" // VQD_ENABLE_OCCLUSION = "vqd_occlusion" // VQD_ENABLE_BLUE = "vqd_blue" // VQD_ENABLE_SHARK = "vqd_shark" // VQD_ENABLE_FREEZE = "vqd_freeze" // VQD_ENABLE_MOSAIC = "vqd_mosaic" // VQD_ENABLE_FLOWER = "vqd_flower" func (v *VQDHandle) parseVQD(result VQDResult) (AbnormalModel, bool) { isabnormal := false abnormals := AbnormalModel{ IsDeep: v.handle.Params.UseDeepLearning, Abnormals: make([]Abnormal, 0), } if (result.AbnormalType & NXU_VQD_ABN_COLORDEV) != 0 { isabnormal = true abnormals.Abnormals = append(abnormals.Abnormals, Abnormal{ Value: result.ColorDev, Name: ALNORMAL_NAMES[NXU_VQD_ABN_COLORDEV], Mode: "vqd_color", }) } abnormals.DefaultValues = append(abnormals.DefaultValues, DefaultValue{ Thr1: v.handle.Params.ColorPara.ColorThr, Name1: "偏色阈值", Ratio: v.handle.Params.ColorPara.ColorAbnNumRatio, }) if (result.AbnormalType & NXU_VQD_ABN_LIGHT) != 0 { isabnormal = true abnormals.Abnormals = append(abnormals.Abnormals, Abnormal{ Value: result.LgtDark, Name: ALNORMAL_NAMES[NXU_VQD_ABN_LIGHT], Mode: "vqd_lgt_light", }) } if (result.AbnormalType & NXU_VQD_ABN_DARK) != 0 { isabnormal = true abnormals.Abnormals = append(abnormals.Abnormals, Abnormal{ Value: result.LgtDark, Name: ALNORMAL_NAMES[NXU_VQD_ABN_DARK], Mode: "vqd_lgt_dark", }) } abnormals.DefaultValues = append(abnormals.DefaultValues, DefaultValue{ Thr1: v.handle.Params.LgtDarkPara.LightThr, Name1: "过亮阈值", Thr2: v.handle.Params.LgtDarkPara.DarkThr, Name2: "过暗阈值", Ratio: v.handle.Params.LgtDarkPara.LgtDarkAbnNumRatio, }) if (result.AbnormalType & NXU_VQD_ABN_CLARITY) != 0 { isabnormal = true abnormals.Abnormals = append(abnormals.Abnormals, Abnormal{ Value: result.Clarity, Name: ALNORMAL_NAMES[NXU_VQD_ABN_CLARITY], Mode: "vqd_clarity", }) } abnormals.DefaultValues = append(abnormals.DefaultValues, DefaultValue{ Thr1: v.handle.Params.ClarityPara.ClarityThr, Name1: "清晰度阈值", Ratio: v.handle.Params.ClarityPara.ClarityAbnNumRatio, }) if (result.AbnormalType & NXU_VQD_ABN_NOISE) != 0 { isabnormal = true abnormals.Abnormals = append(abnormals.Abnormals, Abnormal{ Value: result.Noise, Name: ALNORMAL_NAMES[NXU_VQD_ABN_NOISE], Mode: "vqd_noise", }) } abnormals.DefaultValues = append(abnormals.DefaultValues, DefaultValue{ Thr1: v.handle.Params.NoisePara.NoiseThr, Name1: "噪声阈值", Ratio: v.handle.Params.NoisePara.NoiseAbnNumRatio, }) if (result.AbnormalType & NXU_VQD_ABN_CONTRAST) != 0 { isabnormal = true abnormals.Abnormals = append(abnormals.Abnormals, Abnormal{ Value: result.Contrast, Name: ALNORMAL_NAMES[NXU_VQD_ABN_CONTRAST], Mode: "vqd_contrast", }) } abnormals.DefaultValues = append(abnormals.DefaultValues, DefaultValue{ Thr1: v.handle.Params.ContrastPara.CtraLowThr, Name1: "低对比度阈值", Thr2: v.handle.Params.ContrastPara.CtraHighThr, Name2: "高对比度阈值", Ratio: v.handle.Params.ContrastPara.CtraAbnNumRatio, }) if (result.AbnormalType & NXU_VQD_ABN_OCCLUSION) != 0 { isabnormal = true abnormals.Abnormals = append(abnormals.Abnormals, Abnormal{ Value: result.Occlusion, Name: ALNORMAL_NAMES[NXU_VQD_ABN_OCCLUSION], Mode: "vqd_occlusion", }) } abnormals.DefaultValues = append(abnormals.DefaultValues, DefaultValue{ Thr1: v.handle.Params.OcclusionPara.OcclusionThr, Name1: "遮挡阈值", Ratio: v.handle.Params.OcclusionPara.OcclusionAbnNumRatio, }) if (result.AbnormalType & NXU_VQD_ABN_BLUE) != 0 { isabnormal = true abnormals.Abnormals = append(abnormals.Abnormals, Abnormal{ Value: result.Blue, Name: ALNORMAL_NAMES[NXU_VQD_ABN_BLUE], Mode: "vqd_blue", }) } abnormals.DefaultValues = append(abnormals.DefaultValues, DefaultValue{ Thr1: v.handle.Params.BluePara.BlueThr, Name1: "蓝屏阈值", Ratio: v.handle.Params.BluePara.BlueAbnNumRatio, }) if (result.AbnormalType & NXU_VQD_ABN_SHARK) != 0 { isabnormal = true abnormals.Abnormals = append(abnormals.Abnormals, Abnormal{ Value: result.Shark, Name: ALNORMAL_NAMES[NXU_VQD_ABN_SHARK], Mode: "vqd_shark", }) } abnormals.DefaultValues = append(abnormals.DefaultValues, DefaultValue{ Thr1: v.handle.Params.SharkPara.SharkThr, Name1: "抖动阈值", Ratio: v.handle.Params.SharkPara.SharkAbnNumRatio, }) if (result.AbnormalType & NXU_VQD_ABN_FREEZE) != 0 { isabnormal = true abnormals.Abnormals = append(abnormals.Abnormals, Abnormal{ Value: result.Freeze, Name: ALNORMAL_NAMES[NXU_VQD_ABN_FREEZE], Mode: "vqd_freeze", }) } abnormals.DefaultValues = append(abnormals.DefaultValues, DefaultValue{ Thr1: v.handle.Params.FreezePara.FreezeThr, Name1: "冻结阈值", Ratio: v.handle.Params.FreezePara.FreezeAbnNumRatio, }) if (result.AbnormalType & NXU_VQD_ABN_MOSAIC) != 0 { isabnormal = true abnormals.Abnormals = append(abnormals.Abnormals, Abnormal{ Value: result.Mosaic, Name: ALNORMAL_NAMES[NXU_VQD_ABN_MOSAIC], Mode: "vqd_mosaic", }) } abnormals.DefaultValues = append(abnormals.DefaultValues, DefaultValue{ Thr1: v.handle.Params.MosaicPara.MosaicThr, Name1: "马赛克阈值", Ratio: v.handle.Params.MosaicPara.MosaicAbnNumRatio, }) if (result.AbnormalType & NXU_VQD_ABN_FLOWER) != 0 { isabnormal = true abnormals.Abnormals = append(abnormals.Abnormals, Abnormal{ Value: result.Flower, Name: ALNORMAL_NAMES[NXU_VQD_ABN_FLOWER], Mode: "vqd_flower", }) } abnormals.DefaultValues = append(abnormals.DefaultValues, DefaultValue{ Thr1: v.handle.Params.FlowerPara.FlowerThr, Name1: "花屏阈值", Ratio: v.handle.Params.FlowerPara.FlowerAbnNumRatio, }) return abnormals, isabnormal } func (v *VQDHandle) SendData(buf []byte, _codec int) { if !IsCurTimeInRecordPlan(v.info.Plans, time.Now()) { return } w, h, data, err := v.decoder.PushDataEx(buf, _codec) if err != nil { slog.Error("I帧转YUV失败: ", "TaskID", v.info.TaskID, "err", err) return } if len(data) > 0 && v.data != nil { dst := make([]byte, len(data)) copy(dst, data) now := time.Now() d := ChanData{ data: dst, w: w, h: h, now: now, } v.data <- d } } // IFrameData 封装I帧数据和指针信息 type IFrameData struct { Data []byte // I帧原始字节数据 Pointer unsafe.Pointer // 指向数据的原始指针 Length int // 数据长度(字节数) IsValid bool // 指针是否有效 } // GetIFramePointer 将字节切片转换为原始指针 // 注意:unsafe包的使用会绕过Go的内存安全检查,需谨慎 func GetIFramePointer(data []byte) *IFrameData { if len(data) == 0 { return &IFrameData{ 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 &IFrameData{ Data: data, Pointer: ptr, Length: len(data), IsValid: true, } } func CWD() string { geTwd, err := os.Getwd() if err != nil { return "" } return geTwd }