123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233 |
- package record
- import (
- "io"
- "net"
- "os"
- "path/filepath"
- "strconv"
- "time"
- "go.uber.org/zap"
- . "m7s.live/engine/v4"
- "m7s.live/engine/v4/codec"
- "m7s.live/engine/v4/util"
- )
- type FLVRecorder struct {
- Recorder
- filepositions []uint64
- times []float64
- Offset int64
- duration int64
- }
- func (r *FLVRecorder) Start(streamPath string) (err error) {
- r.Record = &RecordPluginConfig.Flv
- r.ID = streamPath + "/flv"
- if _, ok := RecordPluginConfig.recordings.Load(r.ID); ok {
- return ErrRecordExist
- }
- return plugin.Subscribe(streamPath, r)
- }
- func (r *FLVRecorder) start() {
- RecordPluginConfig.recordings.Store(r.ID, r)
- r.PlayFLV()
- RecordPluginConfig.recordings.Delete(r.ID)
- }
- func (r *FLVRecorder) writeMetaData(file *os.File, duration int64) {
- defer file.Close()
- at, vt := r.Audio, r.Video
- hasAudio, hasVideo := at != nil, vt != nil
- var amf util.AMF
- metaData := util.EcmaArray{
- "MetaDataCreator": "m7s " + Engine.Version,
- "hasVideo": hasVideo,
- "hasAudio": hasAudio,
- "hasMatadata": true,
- "canSeekToEnd": false,
- "duration": float64(duration) / 1000,
- "hasKeyFrames": len(r.filepositions) > 0,
- "filesize": 0,
- }
- var flags byte
- if hasAudio {
- flags |= (1 << 2)
- metaData["audiocodecid"] = int(at.CodecID)
- metaData["audiosamplerate"] = at.SampleRate
- metaData["audiosamplesize"] = at.SampleSize
- metaData["stereo"] = at.Channels == 2
- }
- if hasVideo {
- flags |= 1
- metaData["videocodecid"] = int(vt.CodecID)
- metaData["width"] = vt.SPSInfo.Width
- metaData["height"] = vt.SPSInfo.Height
- metaData["framerate"] = vt.FPS
- metaData["videodatarate"] = vt.BPS
- metaData["keyframes"] = map[string]any{
- "filepositions": r.filepositions,
- "times": r.times,
- }
- defer func() {
- r.filepositions = []uint64{0}
- r.times = []float64{0}
- }()
- }
- amf.Marshals("onMetaData", metaData)
- offset := amf.Len() + len(codec.FLVHeader) + 15
- if keyframesCount := len(r.filepositions); keyframesCount > 0 {
- metaData["filesize"] = uint64(offset) + r.filepositions[keyframesCount-1]
- for i := range r.filepositions {
- r.filepositions[i] += uint64(offset)
- }
- metaData["keyframes"] = map[string]any{
- "filepositions": r.filepositions,
- "times": r.times,
- }
- }
- if tempFile, err := os.CreateTemp("", "*.flv"); err != nil {
- r.Error("create temp file failed: ", zap.Error(err))
- return
- } else {
- defer func() {
- tempFile.Close()
- os.Remove(tempFile.Name())
- r.Info("writeMetaData success")
- }()
- _, err := tempFile.Write([]byte{'F', 'L', 'V', 0x01, flags, 0, 0, 0, 9, 0, 0, 0, 0})
- if err != nil {
- r.Error("", zap.Error(err))
- return
- }
- amf.Reset()
- marshals := amf.Marshals("onMetaData", metaData)
- codec.WriteFLVTag(tempFile, codec.FLV_TAG_TYPE_SCRIPT, 0, marshals)
- _, err = file.Seek(int64(len(codec.FLVHeader)), io.SeekStart)
- if err != nil {
- r.Error("writeMetaData Seek failed: ", zap.Error(err))
- return
- }
- _, err = io.Copy(tempFile, file)
- if err != nil {
- r.Error("writeMetaData Copy failed: ", zap.Error(err))
- return
- }
- tempFile.Seek(0, io.SeekStart)
- file.Seek(0, io.SeekStart)
- _, err = io.Copy(file, tempFile)
- if err != nil {
- r.Error("writeMetaData Copy failed: ", zap.Error(err))
- return
- }
- }
- }
- func (r *FLVRecorder) OnEvent(event any) {
- switch v := event.(type) {
- case ISubscriber:
- filename := strconv.FormatInt(time.Now().Unix(), 10) + r.Ext
- if r.Fragment == 0 {
- filename = r.Stream.Path + r.Ext
- } else {
- filename = filepath.Join(r.Stream.Path, filename)
- }
- if file, err := r.CreateFileFn(filename, r.append); err == nil {
- r.SetIO(file)
- // 写入文件头
- if !r.append {
- r.Write(codec.FLVHeader)
- } else {
- if _, err = file.Seek(-4, io.SeekEnd); err != nil {
- r.Error("seek file failed", zap.Error(err))
- r.Write(codec.FLVHeader)
- } else {
- tmp := make(util.Buffer, 4)
- tmp2 := tmp
- file.Read(tmp)
- tagSize := tmp.ReadUint32()
- tmp = tmp2
- file.Seek(int64(tagSize), io.SeekEnd)
- file.Read(tmp2)
- ts := tmp2.ReadUint24() | (uint32(tmp[3]) << 24)
- r.Info("append flv", zap.String("filename", filename), zap.Uint32("last tagSize", tagSize), zap.Uint32("last ts", ts))
- if r.VideoReader != nil {
- r.VideoReader.StartTs = time.Duration(ts) * time.Millisecond
- }
- if r.AudioReader != nil {
- r.AudioReader.StartTs = time.Duration(ts) * time.Millisecond
- }
- file.Seek(0, io.SeekEnd)
- }
- }
- go r.start()
- } else {
- r.Error("create file failed", zap.Error(err))
- r.Stop()
- }
- case FLVFrame:
- check := false
- var absTime uint32
- if r.VideoReader == nil {
- check = true
- absTime = r.AudioReader.AbsTime
- } else if v.IsVideo() {
- check = r.VideoReader.Value.IFrame
- absTime = r.VideoReader.AbsTime
- if check {
- r.filepositions = append(r.filepositions, uint64(r.Offset))
- r.times = append(r.times, float64(absTime)/1000)
- }
- }
- if r.duration = int64(absTime); r.Fragment > 0 && check && time.Duration(r.duration)*time.Millisecond >= r.Fragment {
- r.Close()
- r.Offset = 0
- if file, err := r.CreateFileFn(filepath.Join(r.Stream.Path, strconv.FormatInt(time.Now().Unix(), 10)+r.Ext), false); err == nil {
- r.SetIO(file)
- r.Write(codec.FLVHeader)
- var dcflv net.Buffers
- if r.VideoReader != nil {
- r.VideoReader.ResetAbsTime()
- dcflv = codec.VideoAVCC2FLV(0, r.VideoReader.Track.SequenceHead)
- flv := append(dcflv, codec.VideoAVCC2FLV(0, r.VideoReader.Value.AVCC.ToBuffers()...)...)
- flv.WriteTo(r)
- }
- if r.AudioReader != nil {
- r.AudioReader.ResetAbsTime()
- if r.Audio.CodecID == codec.CodecID_AAC {
- dcflv = codec.AudioAVCC2FLV(0, r.AudioReader.Track.SequenceHead)
- }
- flv := append(dcflv, codec.AudioAVCC2FLV(0, r.AudioReader.Value.AVCC.ToBuffers()...)...)
- flv.WriteTo(r)
- }
- return
- }
- }
- if n, err := v.WriteTo(r); err != nil {
- r.Error("write file failed", zap.Error(err))
- r.Stop()
- } else {
- r.Offset += n
- }
- default:
- r.Subscriber.OnEvent(event)
- }
- }
- func (r *FLVRecorder) SetIO(file any) {
- r.Subscriber.SetIO(file)
- r.Closer = r
- }
- func (r *FLVRecorder) Close() error {
- if file, ok := r.Writer.(*os.File); ok && !r.append {
- go r.writeMetaData(file, r.duration)
- } else if closer, ok := r.Writer.(io.Closer); ok {
- return closer.Close()
- }
- return nil
- }
|