package hls import ( "fmt" "os" "path/filepath" "time" "gorm.io/gorm" "m7s.live/v5" "m7s.live/v5/pkg" "m7s.live/v5/pkg/codec" "m7s.live/v5/pkg/config" "m7s.live/v5/pkg/task" "m7s.live/v5/pkg/util" mpegts "m7s.live/v5/plugin/hls/pkg/ts" ) func NewRecorder(conf config.Record) m7s.IRecorder { return &Recorder{} } type Recorder struct { m7s.DefaultRecorder stream m7s.RecordStream ts *TsInFile pesAudio *mpegts.MpegtsPESFrame pesVideo *mpegts.MpegtsPESFrame segmentCount uint32 lastTs time.Duration firstSegment bool } var CustomFileName = func(job *m7s.RecordJob) string { if job.RecConf.Fragment == 0 || job.RecConf.Append { return fmt.Sprintf("%s/%s.ts", job.RecConf.FilePath, time.Now().Format("20060102150405")) } return filepath.Join(job.RecConf.FilePath, time.Now().Format("20060102150405")+".ts") } func (r *Recorder) createStream(start time.Time) (err error) { recordJob := &r.RecordJob sub := recordJob.Subscriber r.stream = m7s.RecordStream{ StartTime: start, StreamPath: sub.StreamPath, FilePath: CustomFileName(&r.RecordJob), EventId: recordJob.EventId, EventDesc: recordJob.EventDesc, EventName: recordJob.EventName, EventLevel: recordJob.EventLevel, BeforeDuration: recordJob.BeforeDuration, AfterDuration: recordJob.AfterDuration, Mode: recordJob.Mode, Type: "hls", } dir := filepath.Dir(r.stream.FilePath) dir = filepath.Clean(dir) if err = os.MkdirAll(dir, 0755); err != nil { r.Error("create directory failed", "err", err, "dir", dir) return } if sub.Publisher.HasAudioTrack() { r.stream.AudioCodec = sub.Publisher.AudioTrack.ICodecCtx.String() } if sub.Publisher.HasVideoTrack() { r.stream.VideoCodec = sub.Publisher.VideoTrack.ICodecCtx.String() } if recordJob.Plugin.DB != nil { recordJob.Plugin.DB.Save(&r.stream) } return } type eventRecordCheck struct { task.Task DB *gorm.DB streamPath string } func (t *eventRecordCheck) Run() (err error) { var eventRecordStreams []m7s.RecordStream queryRecord := m7s.RecordStream{ EventLevel: m7s.EventLevelHigh, Mode: m7s.RecordModeEvent, Type: "hls", } t.DB.Where(&queryRecord).Find(&eventRecordStreams, "stream_path=?", t.streamPath) //搜索事件录像,且为重要事件(无法自动删除) if len(eventRecordStreams) > 0 { for _, recordStream := range eventRecordStreams { var unimportantEventRecordStreams []m7s.RecordStream queryRecord.EventLevel = m7s.EventLevelLow query := `(start_time BETWEEN ? AND ?) OR (end_time BETWEEN ? AND ?) OR (? BETWEEN start_time AND end_time) OR (? BETWEEN start_time AND end_time) AND stream_path=? ` t.DB.Where(&queryRecord).Where(query, recordStream.StartTime, recordStream.EndTime, recordStream.StartTime, recordStream.EndTime, recordStream.StartTime, recordStream.EndTime, recordStream.StreamPath).Find(&unimportantEventRecordStreams) if len(unimportantEventRecordStreams) > 0 { for _, unimportantEventRecordStream := range unimportantEventRecordStreams { unimportantEventRecordStream.EventLevel = m7s.EventLevelHigh t.DB.Save(&unimportantEventRecordStream) } } } } return } func (r *Recorder) writeTailer(end time.Time) { if r.stream.EndTime.After(r.stream.StartTime) { return } r.stream.EndTime = end if r.RecordJob.Plugin.DB != nil { r.RecordJob.Plugin.DB.Save(&r.stream) } } func (r *Recorder) Dispose() { // 如果当前有未完成的片段,先保存 if r.ts != nil { r.ts.Close() } r.writeTailer(time.Now()) } func (r *Recorder) createNewTs() { var oldPMT util.Buffer if r.ts != nil { oldPMT = r.ts.PMT r.ts.Close() } var err error r.ts, err = NewTsInFile(r.stream.FilePath) if err != nil { r.Error("create ts file failed", "err", err, "path", r.stream.FilePath) return } if oldPMT.Len() > 0 { r.ts.PMT = oldPMT } } func (r *Recorder) writeSegment(ts time.Duration) (err error) { if dur := ts - r.lastTs; dur >= r.RecordJob.RecConf.Fragment || r.lastTs == 0 { if dur == ts && r.lastTs == 0 { //时间戳不对的情况,首个默认为2s dur = time.Duration(2) * time.Second } // 如果是第一个片段,跳过写入,只记录时间戳 if r.firstSegment { r.lastTs = ts r.firstSegment = false return nil } // 结束当前片段的记录 r.writeTailer(time.Now()) // 创建新的数据库记录 err = r.createStream(time.Now()) if err != nil { return } // 创建新的ts文件 r.createNewTs() r.segmentCount++ r.lastTs = ts } return } func (r *Recorder) Run() (err error) { ctx := &r.RecordJob suber := ctx.Subscriber startTime := time.Now() if ctx.BeforeDuration > 0 { startTime = startTime.Add(-ctx.BeforeDuration) } // 创建第一个片段记录 if err = r.createStream(startTime); err != nil { return } // 初始化HLS相关结构 r.createNewTs() r.pesAudio = &mpegts.MpegtsPESFrame{ Pid: mpegts.PID_AUDIO, } r.pesVideo = &mpegts.MpegtsPESFrame{ Pid: mpegts.PID_VIDEO, } r.firstSegment = true var audioCodec, videoCodec codec.FourCC if suber.Publisher.HasAudioTrack() { audioCodec = suber.Publisher.AudioTrack.FourCC() } if suber.Publisher.HasVideoTrack() { videoCodec = suber.Publisher.VideoTrack.FourCC() } r.ts.WritePMTPacket(audioCodec, videoCodec) return m7s.PlayBlock(suber, r.ProcessADTS, r.ProcessAnnexB) } func (r *Recorder) ProcessADTS(audio *pkg.ADTS) (err error) { return r.ts.WriteAudioFrame(audio, r.pesAudio) } func (r *Recorder) ProcessAnnexB(video *pkg.AnnexB) (err error) { if r.RecordJob.Subscriber.VideoReader.Value.IDR { if err = r.writeSegment(video.GetTimestamp()); err != nil { return } } return r.ts.WriteVideoFrame(video, r.pesVideo) }