123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210 |
- package record
- import (
- "io"
- "net"
- "os"
- "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 NewFLVRecorder() (r *FLVRecorder) {
- r = &FLVRecorder{}
- r.Record = RecordPluginConfig.Flv
- return r
- }
- func (r *FLVRecorder) Start(streamPath string) (err error) {
- r.ID = streamPath + "/flv"
- return r.start(r, streamPath, SUBTYPE_FLV)
- }
- func (r *FLVRecorder) writeMetaData(file FileWr, 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) {
- r.Recorder.OnEvent(event)
- switch v := event.(type) {
- case FileWr:
- // 写入文件头
- if !r.append {
- v.Write(codec.FLVHeader)
- } else {
- if _, err := v.Seek(-4, io.SeekEnd); err != nil {
- r.Error("seek file failed", zap.Error(err))
- v.Write(codec.FLVHeader)
- } else {
- tmp := make(util.Buffer, 4)
- tmp2 := tmp
- v.Read(tmp)
- tagSize := tmp.ReadUint32()
- tmp = tmp2
- v.Seek(int64(tagSize), io.SeekEnd)
- v.Read(tmp2)
- ts := tmp2.ReadUint24() | (uint32(tmp[3]) << 24)
- r.Info("append flv", 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
- }
- v.Seek(0, io.SeekEnd)
- }
- }
- 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.CreateFile(); err == nil {
- r.File = file
- file.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(file)
- }
- 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(file)
- }
- return
- }
- }
- if n, err := v.WriteTo(r.File); err != nil {
- r.Error("write file failed", zap.Error(err))
- r.Stop(zap.Error(err))
- } else {
- r.Offset += n
- }
- }
- }
- func (r *FLVRecorder) Close() error {
- if r.File != nil {
- if !r.append {
- go r.writeMetaData(r.File, r.duration)
- } else {
- return r.File.Close()
- }
- }
- return nil
- }
|