package main import ( "errors" "io/ioutil" "net/http" "os" "strconv" "strings" "time" "github.com/go-resty/resty/v2" "lc/common/mqtt" "lc/common/onvif/profiles/media" "lc/common/protocol" "lc/common/util" ) var ( AppLive = "live" UploadSnapshot = "/camera/v1/snapshot" UploadFlv = "/camera/v1/flv" UploadPresetSnapshot = "/camera/v1/preset/picture" ) func (o *LcDevice) GetProfiles() error { XAddr, ok := o.endpoints["media"] if !ok { return errors.New("不支持media") } m := media.NewMedia(o.client, XAddr) reply, err := m.GetProfiles(&media.GetProfiles{}) if err != nil { return err } for _, v := range reply.Profiles { vt := VideoToken{ Token: string(v.Token), Name: string(v.Name), VideoEncoding: string(v.VideoEncoderConfiguration.Encoding), VideoResolution: Resolution{ Width: v.VideoEncoderConfiguration.Resolution.Width, Height: v.VideoEncoderConfiguration.Resolution.Height, }, VideoFrameRateLimit: v.VideoEncoderConfiguration.RateControl.FrameRateLimit, VideoBitrateLimit: v.VideoEncoderConfiguration.RateControl.BitrateLimit, } o.tokens[vt.Token] = &vt } return nil } func (o *LcDevice) GetAllStreamUri() error { XAddr, ok := o.endpoints["media"] if !ok { return errors.New("不支持media") } m := media.NewMedia(o.client, XAddr) for k, v := range o.tokens { for retry := 3; retry > 0; retry-- { reply, err := m.GetStreamUri(&media.GetStreamUri{ProfileToken: media.ReferenceToken(k)}) if err != nil { time.Sleep(3 * time.Second) } else { v.VideoRtspUrl = strings.ReplaceAll(string(reply.MediaUri.Uri), "rtsp://", "rtsp://"+o.onvifDev.User+":"+o.onvifDev.Password+"@") break } } } return nil } func (o *LcDevice) GetAllSnapshotUri() error { XAddr, ok := o.endpoints["media"] if !ok { return errors.New("不支持media") } m := media.NewMedia(o.client, XAddr) for k, v := range o.tokens { for retry := 3; retry > 0; retry-- { reply, err := m.GetSnapshotUri(&media.GetSnapshotUri{ProfileToken: media.ReferenceToken(k)}) if err != nil { //其他错误停3秒重试 time.Sleep(5 * time.Second) } else { v.SnapshotUrl = string(reply.MediaUri.Uri) break } } } return nil } // SnapshotFromRtsp 通过RTSP截取一张图片 func (o *LcDevice) SnapshotFromRtsp(token string) (string, error) { RtspUrl := o.GetRtspUrl(token) if RtspUrl == "" { return "", errors.New("找不到RtspUrl") } wd, _ := os.Getwd() proc := &os.ProcAttr{ Dir: wd, Env: os.Environ(), Files: []*os.File{ os.Stdin, os.Stdout, os.Stderr, }, } fileName := util.GetPath(4) + o.onvifDev.Code + "_" + strconv.FormatInt(util.MlNow().Unix(), 10) + ".jpg" args := []string{onvifDevConfig.Ffmpeg, "-i", RtspUrl, "-t", "0.001", "-y", "-f", "mjpeg", "-r", "1", fileName} process, err := os.StartProcess(onvifDevConfig.Ffmpeg, args, proc) if err != nil { return "", err } else { go func() { time.Sleep(6 * time.Second) if process != nil { process.Signal(os.Kill) } }() _, err = process.Wait() process = nil } //判断是否已经抓图成功 if FileExist(fileName) { return fileName, nil } err = errors.New("snapshotFromRtsp:抓图失败") return "", err } func (o *LcDevice) GetRtspUrl(token string) string { rtspUrl := "" if v, ok := o.tokens[token]; !ok { max := int32(0) for _, v := range o.tokens { //查找分辨率最高的SnapshotUrl r := v.VideoResolution.Height * v.VideoResolution.Width if r > max { max = r rtspUrl = v.VideoRtspUrl } } } else { rtspUrl = v.VideoRtspUrl } return rtspUrl } func (o *LcDevice) GetSnapshotUrl(token string) string { snapshotUrl := "" max := int32(0) if v, ok := o.tokens[token]; !ok { for _, v := range o.tokens { //查找分辨率最高的SnapshotUrl r := v.VideoResolution.Height * v.VideoResolution.Width if r > max { max = r snapshotUrl = v.SnapshotUrl } } } else { snapshotUrl = v.SnapshotUrl } return snapshotUrl } func (o *LcDevice) GetProfileToken(token string) string { max := int32(0) if _, ok := o.tokens[token]; !ok { for _, v := range o.tokens { //查找分辨率最高的SnapshotUrl r := v.VideoResolution.Height * v.VideoResolution.Width if r > max { max = r token = v.Token } } } return token } func (o *LcDevice) Snapshot(token string) { snapshotUrl := o.GetSnapshotUrl(token) if snapshotUrl == "" { return } var fileName string client := resty.New() client.SetTimeout(6 * time.Second) client.SetBasicAuth(o.onvifDev.User, o.onvifDev.Password) resp, err := client.R().Get(snapshotUrl) if err != nil { fileName, err = o.SnapshotFromRtsp(token) } else { if resp.StatusCode() != http.StatusOK { fileName, err = o.SnapshotFromRtsp(token) } else if len(resp.Body()) == 0 { fileName, err = o.SnapshotFromRtsp(token) } else { fileName = util.GetPath(4) + o.onvifDev.Code + "_" + strconv.FormatInt(util.MlNow().Unix(), 10) + ".jpg" if err = ioutil.WriteFile(fileName, resp.Body(), os.ModePerm); err != nil { return } } } if fileName != "" && err == nil { client.R().SetFile("file", fileName).Post(o.onvifDev.WebServer + UploadSnapshot) os.Remove(fileName) } } func (o *LcDevice) Snapshot2(file string) error { snapshotUrl := o.GetSnapshotUrl("") if snapshotUrl == "" { return errors.New("找不到链接,截图错误") } var fileName string client := resty.New() client.SetTimeout(6 * time.Second) client.SetBasicAuth(o.onvifDev.User, o.onvifDev.Password) resp, err := client.R().Get(snapshotUrl) if err != nil { return err } if resp.StatusCode() != http.StatusOK { return errors.New("调用截图接口发生错误") } fileName = util.GetPath(4) + file err = ioutil.WriteFile(fileName, resp.Body(), os.ModePerm) if err != nil { return err } resp, err = client.R().SetFile("file", fileName).Post(o.onvifDev.WebServer + UploadPresetSnapshot) os.Remove(fileName) if err != nil { return err } if resp.StatusCode() != http.StatusOK { return errors.New("截图上传发生错误") } return nil } func (o *LcDevice) CreateProcess(rtspurl string) { dir, _ := os.Getwd() proc := &os.ProcAttr{ Dir: dir, Env: os.Environ(), Files: []*os.File{ os.Stdin, os.Stdout, os.Stderr, }, } args := []string{onvifDevConfig.Ffmpeg, "-fflags", "nobuffer", "-i", rtspurl, "-c", "copy", "-f", "flv", o.onvifDev.RtmpServer + "/" + AppLive + "/" + o.onvifDev.Code} process, err := os.StartProcess(onvifDevConfig.Ffmpeg, args, proc) if err != nil { return } o.ffmpeg = process go o.WatchFfmpeg() } func (o *LcDevice) CleanupProcess() { if o.ffmpeg != nil { o.ffmpeg.Kill() } } func (o *LcDevice) WatchFfmpeg() { status := make(chan *os.ProcessState) died := make(chan error) go func() { state, err := o.ffmpeg.Wait() if err != nil { died <- err return } status <- state }() select { case _ = <-status: case _ = <-died: } o.ffmpeg = nil } func (o *LcDevice) RecordToFLV(token string, second int) { //最长录制60秒视频 if second > 60 { second = 60 } RtspUrl := o.GetRtspUrl(token) if RtspUrl == "" { return } dir, _ := os.Getwd() proc := &os.ProcAttr{ Dir: dir, Env: os.Environ(), Files: []*os.File{ os.Stdin, os.Stdout, os.Stderr, }, } fileName := util.GetPath(4) + o.onvifDev.Code + "_" + strconv.FormatInt(util.MlNow().Unix(), 10) + ".flv" args := []string{onvifDevConfig.Ffmpeg, "-i", RtspUrl, "-c", "copy", "-t", strconv.Itoa(second), fileName} process, err := os.StartProcess(onvifDevConfig.Ffmpeg, args, proc) if err != nil { return } else if process != nil { go func() { time.Sleep(time.Duration(second+5) * time.Second) if process != nil { process.Signal(os.Kill) } }() _, err = process.Wait() //判断是否已经抓图成功 if fileName != "" && err == nil { if FileExist(fileName) { client := resty.New() client.SetTimeout(6 * time.Second) client.R().SetFile("file", fileName).Post(o.onvifDev.WebServer + UploadFlv) os.Remove(fileName) } } } } // HandleTpOnvifSnapshot 截图 func (o *LcDevice) HandleTpOnvifSnapshot(m mqtt.Message) { var obj protocol.Pack_MediaCommonInfo if err := obj.DeCode(m.PayloadString()); err != nil { return } if o.onvifDev.Code != obj.Id { return } var ret protocol.Pack_IPCCommonACK if strRet, err := ret.EnCode(o.onvifDev.Code, appConfig.GID, "", obj.Seq, nil); err == nil { GetMQTTMgr().Publish(GetTopic(o.GetDevType(), o.onvifDev.Code, protocol.TP_ONVIF_SNAPSHOT_ACK), strRet, mqtt.AtMostOnce, ToAll) } go o.Snapshot(obj.Data.ProfileToken) } // HandleTpOnvifVideo 拉流推流,停止拉流推流 func (o *LcDevice) HandleTpOnvifVideo(m mqtt.Message) { var obj protocol.Pack_MediaCommonInfo if err := obj.DeCode(m.PayloadString()); err != nil { return } if o.onvifDev.Code != obj.Id { return } var ret protocol.Pack_IPCCommonACK if strRet, err := ret.EnCode(o.onvifDev.Code, appConfig.GID, "", obj.Seq, nil); err == nil { GetMQTTMgr().Publish(GetTopic(o.GetDevType(), o.onvifDev.Code, protocol.TP_ONVIF_VIDEO_ACK), strRet, mqtt.AtMostOnce, ToAll) } //如果已开启GB28181,则不用启动ffmpeg推流 if o.onvifDev.Gb28181 { return } if obj.Data.Flag == 1 { o.observer++ if o.ffmpeg != nil { return } if rtspUrl := o.GetRtspUrl(obj.Data.ProfileToken); rtspUrl == "" { return } else { o.CreateProcess(rtspUrl) } } else if obj.Data.Flag == 2 { //停止推流 if o.observer > 0 { o.observer-- if o.observer == 0 { o.CleanupProcess() } } } } func (o *LcDevice) HandleTpOnvifRecord(m mqtt.Message) { var obj protocol.Pack_MediaCommonInfo if err := obj.DeCode(m.PayloadString()); err != nil { return } if o.onvifDev.Code != obj.Id { return } var ret protocol.Pack_IPCCommonACK if strRet, err := ret.EnCode(o.onvifDev.Code, appConfig.GID, "", obj.Seq, nil); err == nil { GetMQTTMgr().Publish(GetTopic(o.GetDevType(), o.onvifDev.Code, protocol.TP_ONVIF_RECORD_ACK), strRet, mqtt.AtMostOnce, ToAll) } go o.RecordToFLV("", 30) }