EasyVQD/internal/core/vqdtask/core.go
2026-01-23 18:05:36 +08:00

285 lines
7.7 KiB
Go
Raw Permalink 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"
"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
}