EasyVQD/internal/core/vqdtask/core.go
2026-01-27 14:20:50 +08:00

330 lines
8.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package vqdtask
import (
"bufio"
"context"
"easyvqd/internal/conf"
"easyvqd/internal/core/host"
"easyvqd/internal/core/vqd"
"easyvqd/pkg/vqdcms"
"fmt"
"log/slog"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
)
type Core struct {
HostCore *host.Core
VqdTaskCore *vqd.Core
Cfg *conf.Bootstrap
ResultCb vqdcms.VQDResultCB
}
var (
VqdTaskMap = vqdcms.VqdTaskMap{M: make(map[string]*vqdcms.VQDHandle)}
)
func NewCore(HostCore *host.Core, VqdTaskCore *vqd.Core, Cfg *conf.Bootstrap) *Core {
core := &Core{
HostCore: HostCore,
VqdTaskCore: VqdTaskCore,
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) {
if codes == VIDEO_CODEC_H264 {
v, ok := VqdTaskMap.LoadTaskMap(s)
if ok {
v.SendData(data, VIDEO_CODEC_H264)
}
} else {
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() {
// 启用诊断分析
core.InitVqdTask()
})
// 启用定时清理任务
go core.scheduleCleanTask()
// 启用任务管理器
return core
}
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 {
errs := c.AddTaskVqd(vqdTask.ID)
if errs != nil {
slog.Error("vqd init add task", "err", errs.Error())
}
time.Sleep(200 * time.Millisecond)
}
}
return
}
func (c *Core) UnVqdTask() {
VqdTaskMap.DeleteTaskMapAll()
vqdcms.VQDUnInit()
return
}
func (c *Core) AddTaskVqd(taskId int) error {
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() {
dir, _ := os.Getwd()
h264Path := filepath.Join(dir, "check_tip.h264") // 你的H.264裸流文件路径
//1. 检查FFmpeg
ok, err := CheckFFmpeg()
if !ok {
fmt.Println("错误:", err)
os.Exit(1)
}
// 2. 配置参数根据你的H.264文件调整)
imageFormat := "jpg" // 输出图片格式
videoWidth := 0 // 分辨率自动探测如需指定则改为实际值如1920
videoHeight := 0 // 分辨率自动探测如需指定则改为实际值如1080
// 3. 提取H.264关键帧并转图片
if err := ExtractH264KeyFrames(h264Path, dir, imageFormat, videoWidth, videoHeight); err != nil {
fmt.Println("提取H.264关键帧失败:", err)
os.Exit(1)
}
}
// CheckFFmpeg 检查系统是否安装了FFmpeg
func CheckFFmpeg() (bool, error) {
cmd := exec.Command("ffmpeg", "-version")
err := cmd.Run()
if err != nil {
return false, fmt.Errorf("FFmpeg 未安装或未添加到系统PATH: %v", err)
}
return true, nil
}
// ExtractH264KeyFrames 将H.264裸流的关键帧转为图片
// h264Path: H.264裸流文件路径(.264/.h264
// outputDir: 图片输出目录
// format: 输出图片格式 (jpg/png)
// width/height: 视频分辨率若不指定FFmpeg会自动探测
func ExtractH264KeyFrames(h264Path, outputDir, format string, width, height int) error {
// 验证输入文件
if _, err := os.Stat(h264Path); os.IsNotExist(err) {
return fmt.Errorf("H.264文件不存在: %s", h264Path)
}
// 创建输出目录
if err := os.MkdirAll(outputDir, 0755); err != nil {
return fmt.Errorf("创建输出目录失败: %v", err)
}
// 构造FFmpeg命令适配H.264裸流)
outputPattern := filepath.Join(outputDir, "h264_keyframe_%04d."+format)
args := []string{
"-f", "h264", // 明确输入格式为H.264裸流
"-i", h264Path, // 输入H.264裸流文件
"-skip_frame", "nokey", // 只处理关键帧I帧
"-vsync", "0", // 禁用帧同步保证每个I帧都输出
"-q:v", "2", // 图片质量1-31值越小质量越高
}
// 可选指定分辨率如果FFmpeg自动探测失败时使用
if width > 0 && height > 0 {
args = append(args, "-s", fmt.Sprintf("%dx%d", width, height))
}
// 补全输出参数
args = append(args,
"-f", "image2", // 输出图片序列格式
outputPattern, // 输出文件模板
"-y", // 覆盖已存在的文件
)
// 创建命令并捕获输出
cmd := exec.Command("ffmpeg", args...)
_, err := cmd.StdoutPipe()
if err != nil {
return fmt.Errorf("创建标准输出管道失败: %v", err)
}
stderr, err := cmd.StderrPipe()
if err != nil {
return fmt.Errorf("创建标准错误管道失败: %v", err)
}
// 启动命令
if err := cmd.Start(); err != nil {
return fmt.Errorf("启动FFmpeg失败: %v", err)
}
// 实时打印FFmpeg日志便于调试H.264解析问题)
scanner := bufio.NewScanner(stderr)
for scanner.Scan() {
line := scanner.Text()
fmt.Println("FFmpeg日志:", line)
// 捕获关键错误信息
if strings.Contains(line, "error") && strings.Contains(line, "H.264") {
fmt.Println("⚠️ H.264解析警告:", line)
}
}
// 等待命令执行完成
if err := cmd.Wait(); err != nil {
return fmt.Errorf("FFmpeg执行失败: %v", err)
}
// 统计输出的关键帧图片数量
files, err := filepath.Glob(filepath.Join(outputDir, "h264_keyframe_*."+format))
if err != nil {
return fmt.Errorf("统计输出图片失败: %v", err)
}
fmt.Printf("成功从H.264裸流提取 %d 个关键帧,保存至: %s\n", len(files), outputDir)
return nil
}