EasyVQD/internal/core/vqdtask/core.go

285 lines
7.7 KiB
Go
Raw Normal View History

2026-01-23 18:05:36 +08:00
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
}