Files
plugin-record/flv.go
pg deb8cfedae 1.增加事件录像,事件录像列表接口
2.将下载时间范围内的录像及播放时间范围内的录像参数统一为start和end
3.增加事件录像入数据库功能
2024-08-18 23:28:42 +08:00

230 lines
6.3 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package record
import (
"fmt"
"io"
"net"
"os"
"strconv"
"strings"
"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) StartWithFileName(streamPath string, fileName string) error {
r.ID = fmt.Sprintf("%s/flv/%s", streamPath, fileName)
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": true,
"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 VideoFrame:
if r.VideoReader.Value.IFrame {
go func() { //将视频关键帧的数据存入sqlite数据库中
var flvKeyfram = &FLVKeyframe{FLVFileName: r.Path + "/" + strings.ReplaceAll(r.filePath, "\\", "/"), FrameOffset: r.Offset, FrameAbstime: r.VideoReader.AbsTime}
sqlitedb.Create(flvKeyfram)
}()
r.Info("这是关键帧且取到了r.filePath是" + r.Path + r.filePath)
r.Info("这是关键帧且取到了r.VideoReader.AbsTime是" + strconv.FormatUint(uint64(r.VideoReader.AbsTime), 10))
r.Info("这是关键帧且取到了r.Offset是" + strconv.Itoa(int(r.Offset)))
r.Info("这是关键帧且取到了r.Offset是" + r.Stream.Path)
}
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
}