package vqdtask import ( "bufio" "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 } 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.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) } } 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() { err := vqdcms.VQDInit() if err != nil { slog.Error("vqd cms open", "err", err.Error()) return } return } func (c Core) UnVqdTask() { 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) 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, // } //} // 测试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 }