EasyVQD/internal/core/vqdtask/core.go

358 lines
9.4 KiB
Go
Raw Normal View History

2026-01-23 18:05:36 +08:00
package vqdtask
import (
"bufio"
2026-01-27 10:42:21 +08:00
"context"
2026-01-23 18:05:36 +08:00
"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
2026-01-27 10:42:21 +08:00
ResultCb vqdcms.VQDResultCB
2026-01-23 18:05:36 +08:00
}
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,
}
2026-01-27 10:42:21 +08:00
core.ResultCb = func(v vqdcms.AbnormalModel) {
in := &vqd.AddVqdAlarmInput{
ChannelName: v.ChannelName,
2026-01-28 14:00:33 +08:00
ChannelID: v.ChannelID,
2026-01-27 10:42:21 +08:00
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
2026-01-28 14:00:33 +08:00
var AbnormalStr []string
2026-01-27 10:42:21 +08:00
if len(v.Abnormals) > 0 {
for _, abnormal := range v.Abnormals {
item := vqd.Abnormal{
Value: abnormal.Value,
Name: abnormal.Name,
2026-01-28 14:00:33 +08:00
Mode: abnormal.Mode,
2026-01-27 10:42:21 +08:00
}
2026-01-28 14:00:33 +08:00
AbnormalStr = append(AbnormalStr, abnormal.Mode)
2026-01-27 10:42:21 +08:00
Abnormals = append(Abnormals, item)
}
}
2026-01-28 14:00:33 +08:00
in.AbnormalStr = strings.Join(AbnormalStr, ",")
2026-01-27 10:42:21 +08:00
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())
}
}
2026-01-23 18:05:36 +08:00
core.HostCore.CbIFrame = func(s string, data []byte, codes int) {
2026-01-27 14:20:50 +08:00
if codes == VIDEO_CODEC_H264 {
2026-03-26 10:23:31 +08:00
{
v, ok := VqdTaskMap.LoadTaskMap(s)
if ok {
v.SendData(data, VIDEO_CODEC_H264)
}
}
{
v, ok := VqdPollingTaskMap.LoadTaskMap(fmt.Sprintf("%s_polling", s))
if ok {
v.SendData(data, VIDEO_CODEC_H264)
}
2026-01-27 14:20:50 +08:00
}
} else {
2026-03-26 10:23:31 +08:00
{
v, ok := VqdTaskMap.LoadTaskMap(s)
if ok {
v.SendData(data, VIDEO_CODEC_H265)
}
}
{
v, ok := VqdPollingTaskMap.LoadTaskMap(fmt.Sprintf("%s_polling", s))
if ok {
v.SendData(data, VIDEO_CODEC_H265)
}
2026-01-27 14:20:50 +08:00
}
2026-01-23 18:05:36 +08:00
}
2026-01-27 14:20:50 +08:00
2026-01-27 10:42:21 +08:00
//slog.Debug("cb IFrame", "name", s, "codes", codes)
2026-01-27 14:20:50 +08:00
2026-01-23 18:05:36 +08:00
}
time.AfterFunc(time.Duration(5)*time.Second, func() {
// 启用诊断分析
core.InitVqdTask()
2026-03-26 10:23:31 +08:00
// 启用轮询诊断任务
core.StartPolling()
2026-01-23 18:05:36 +08:00
})
// 启用定时清理任务
go core.scheduleCleanTask()
2026-01-27 10:42:21 +08:00
2026-01-23 18:05:36 +08:00
// 启用任务管理器
return core
}
2026-01-27 10:42:21 +08:00
func (c *Core) InitVqdTask() {
2026-01-23 18:05:36 +08:00
err := vqdcms.VQDInit()
if err != nil {
slog.Error("vqd cms open", "err", err.Error())
return
}
2026-01-27 10:42:21 +08:00
all, _, err := c.VqdTaskCore.FindVqdTaskAll()
if err == nil {
for _, vqdTask := range all {
2026-03-26 10:23:31 +08:00
if vqdTask.Enable {
errs := c.AddTaskVqd(vqdTask.ID)
if errs != nil {
slog.Error("vqd init add task", "err", errs.Error())
}
time.Sleep(200 * time.Millisecond)
2026-01-27 14:20:50 +08:00
}
2026-01-27 10:42:21 +08:00
}
}
2026-01-23 18:05:36 +08:00
return
}
2026-01-27 10:42:21 +08:00
func (c *Core) UnVqdTask() {
VqdTaskMap.DeleteTaskMapAll()
2026-01-23 18:05:36 +08:00
vqdcms.VQDUnInit()
2026-03-26 10:23:31 +08:00
// 停止轮询诊断任务
c.StopPolling()
2026-01-23 18:05:36 +08:00
return
}
2026-01-27 10:42:21 +08:00
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
}
2026-03-26 10:23:31 +08:00
2026-01-27 10:42:21 +08:00
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,
2026-01-23 18:05:36 +08:00
}
2026-01-27 10:42:21 +08:00
v := vqdcms.NewVQDHandle(c.ResultCb, c.HostCore, info).Create(para, taskPlan.Plans)
2026-01-23 18:05:36 +08:00
VqdTaskMap.StoreChildMap(fmt.Sprintf("%s", chnId), v)
2026-01-27 10:42:21 +08:00
return nil
2026-01-23 18:05:36 +08:00
}
2026-01-27 10:42:21 +08:00
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
2026-01-23 18:05:36 +08:00
2026-01-27 10:42:21 +08:00
}
func (c *Core) DelTaskVqd(taskId int, chnId string) {
v, ok := VqdTaskMap.LoadTaskMap(chnId)
if ok {
v.Destroy()
}
VqdTaskMap.DeleteTaskMap(chnId)
}
2026-01-23 18:05:36 +08:00
// 测试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
}