refactor: frame converter and mp4 track improvements

- Refactor frame converter implementation
- Update mp4 track to use ICodex
- General refactoring and code improvements

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
langhuihui
2025-08-04 09:17:12 +08:00
parent b6ee2843b0
commit 8a9fffb987
262 changed files with 20831 additions and 12141 deletions

View File

@@ -4,6 +4,7 @@ import (
"errors"
"fmt"
"io"
"m7s.live/v5/pkg/util"
rtmp "m7s.live/v5/plugin/rtmp/pkg"
)
@@ -17,7 +18,8 @@ func Echo(r io.Reader) (err error) {
if err == nil {
var flvHead [3]byte
var version, flag byte
err = head.NewReader().ReadByteTo(&flvHead[0], &flvHead[1], &flvHead[2], &version, &flag)
r := head.NewReader()
err = r.ReadByteTo(&flvHead[0], &flvHead[1], &flvHead[2], &version, &flag)
if flvHead != [...]byte{'F', 'L', 'V'} {
err = errors.New("not flv file")
} else {
@@ -62,7 +64,7 @@ func Echo(r io.Reader) (err error) {
return err
}
absTS = offsetTs + (timestamp - startTs)
frame.Timestamp = absTS
frame.SetTS32(absTS)
fmt.Println(t, offsetTs, timestamp, startTs, absTS)
switch t {
case FLV_TAG_TYPE_AUDIO:
@@ -71,9 +73,7 @@ func Echo(r io.Reader) (err error) {
frame.Recycle()
case FLV_TAG_TYPE_SCRIPT:
r := frame.NewReader()
amf := &rtmp.AMF{
Buffer: util.Buffer(r.ToBytes()),
}
amf := rtmp.AMF(r.ToBytes())
var obj any
obj, err = amf.Unmarshal()
name := obj

View File

@@ -80,9 +80,7 @@ func ReadMetaData(reader io.Reader) (metaData rtmp.EcmaArray, err error) {
if t == FLV_TAG_TYPE_SCRIPT {
data := make([]byte, dataLen+4)
_, err = io.ReadFull(reader, data)
amf := &rtmp.AMF{
Buffer: util.Buffer(data[1+2+len("onMetaData") : len(data)-4]),
}
amf := rtmp.AMF(data[1+2+len("onMetaData") : len(data)-4])
var obj any
obj, err = amf.Unmarshal()
metaData = obj.(rtmp.EcmaArray)

View File

@@ -16,8 +16,8 @@ type Live struct {
}
func (task *Live) WriteFlvHeader() (err error) {
at, vt := &task.Subscriber.Publisher.AudioTrack, &task.Subscriber.Publisher.VideoTrack
hasAudio, hasVideo := at.AVTrack != nil && task.Subscriber.SubAudio, vt.AVTrack != nil && task.Subscriber.SubVideo
audioCtx, videoCtx := task.Subscriber.Publisher.GetAudioCodecCtx(), task.Subscriber.Publisher.GetVideoCodecCtx()
hasAudio, hasVideo := audioCtx != nil && task.Subscriber.SubAudio, videoCtx != nil && task.Subscriber.SubVideo
var amf rtmp.AMF
amf.Marshal("onMetaData")
metaData := rtmp.EcmaArray{
@@ -35,16 +35,16 @@ func (task *Live) WriteFlvHeader() (err error) {
var flags byte
if hasAudio {
flags |= (1 << 2)
metaData["audiocodecid"] = int(rtmp.ParseAudioCodec(at.FourCC()))
ctx := at.ICodecCtx.(IAudioCodecCtx)
metaData["audiocodecid"] = int(rtmp.ParseAudioCodec(audioCtx.FourCC()))
ctx := audioCtx.(IAudioCodecCtx)
metaData["audiosamplerate"] = ctx.GetSampleRate()
metaData["audiosamplesize"] = ctx.GetSampleSize()
metaData["stereo"] = ctx.GetChannels() == 2
}
if hasVideo {
flags |= 1
metaData["videocodecid"] = int(rtmp.ParseVideoCodec(vt.FourCC()))
ctx := vt.ICodecCtx.(IVideoCodecCtx)
metaData["videocodecid"] = int(rtmp.ParseVideoCodec(videoCtx.FourCC()))
ctx := videoCtx.(IVideoCodecCtx)
metaData["width"] = ctx.Width()
metaData["height"] = ctx.Height()
}
@@ -60,12 +60,12 @@ func (task *Live) rtmpData2FlvTag(t byte, data *rtmp.RTMPData, ts uint32) error
return task.WriteFlvTag(append(net.Buffers{task.b[:]}, data.Memory.Buffers...))
}
func (task *Live) WriteAudioTag(data *rtmp.RTMPAudio, ts uint32) error {
return task.rtmpData2FlvTag(FLV_TAG_TYPE_AUDIO, &data.RTMPData, ts)
func (task *Live) WriteAudioTag(data *rtmp.AudioFrame, ts uint32) error {
return task.rtmpData2FlvTag(FLV_TAG_TYPE_AUDIO, (*rtmp.RTMPData)(data), ts)
}
func (task *Live) WriteVideoTag(data *rtmp.RTMPVideo, ts uint32) error {
return task.rtmpData2FlvTag(FLV_TAG_TYPE_VIDEO, &data.RTMPData, ts)
func (task *Live) WriteVideoTag(data *rtmp.VideoFrame, ts uint32) error {
return task.rtmpData2FlvTag(FLV_TAG_TYPE_VIDEO, (*rtmp.RTMPData)(data), ts)
}
func (task *Live) Run() (err error) {
@@ -73,9 +73,9 @@ func (task *Live) Run() (err error) {
if err != nil {
return
}
err = m7s.PlayBlock(task.Subscriber, func(audio *rtmp.RTMPAudio) error {
err = m7s.PlayBlock(task.Subscriber, func(audio *rtmp.AudioFrame) error {
return task.WriteAudioTag(audio, task.Subscriber.AudioReader.AbsTime)
}, func(video *rtmp.RTMPVideo) error {
}, func(video *rtmp.VideoFrame) error {
return task.WriteVideoTag(video, task.Subscriber.VideoReader.AbsTime)
})
if err != nil {

View File

@@ -5,6 +5,7 @@ import (
"io"
"m7s.live/v5"
pkg "m7s.live/v5/pkg"
"m7s.live/v5/pkg/util"
rtmp "m7s.live/v5/plugin/rtmp/pkg"
)
@@ -14,6 +15,10 @@ type Puller struct {
}
func (p *Puller) Run() (err error) {
pullJob := &p.PullJob
// Move to parsing step
pullJob.GoToStepConst(pkg.StepParsing)
reader := util.NewBufReader(p.ReadCloser)
publisher := p.PullJob.Publisher
if publisher == nil {
@@ -27,7 +32,8 @@ func (p *Puller) Run() (err error) {
if err == nil {
var flvHead [3]byte
var version, flag byte
err = head.NewReader().ReadByteTo(&flvHead[0], &flvHead[1], &flvHead[2], &version, &flag)
r := head.NewReader()
err = r.ReadByteTo(&flvHead[0], &flvHead[1], &flvHead[2], &version, &flag)
if flvHead != [...]byte{'F', 'L', 'V'} {
err = errors.New("not flv file")
} else {
@@ -44,6 +50,12 @@ func (p *Puller) Run() (err error) {
pubConf.PubVideo = false
}
allocator := util.NewScalableMemoryAllocator(1 << 10)
defer allocator.Recycle()
writer := m7s.NewPublisherWriter[*rtmp.AudioFrame, *rtmp.VideoFrame](publisher, allocator)
// Move to streaming step
pullJob.GoToStepConst(pkg.StepStreaming)
for offsetTs := absTS; err == nil; _, err = reader.ReadBE(4) {
if p.IsStopped() {
return p.StopReason()
@@ -71,40 +83,50 @@ func (p *Puller) Run() (err error) {
if _, err = reader.ReadBE(3); err != nil { // stream id always 0
return err
}
var frame rtmp.RTMPData
ds := int(dataSize)
frame.SetAllocator(allocator)
err = reader.ReadNto(ds, frame.NextN(ds))
if err != nil {
return err
}
absTS = offsetTs + (timestamp - startTs)
frame.Timestamp = absTS
//fmt.Println(t, offsetTs, timestamp, startTs, puller.absTS)
switch t {
case FLV_TAG_TYPE_AUDIO:
if publisher.PubAudio {
if err = publisher.WriteAudio(frame.WrapAudio()); err != nil {
frame := writer.AudioFrame
_, err = reader.Read(frame.NextN(ds))
if err != nil {
return err
}
frame.SetTS32(absTS)
if err = writer.NextAudio(); err != nil {
return err
}
} else {
reader.Skip(ds)
}
case FLV_TAG_TYPE_VIDEO:
if publisher.PubVideo {
if err = publisher.WriteVideo(frame.WrapVideo()); err != nil {
frame := writer.VideoFrame
_, err = reader.Read(frame.NextN(ds))
if err != nil {
return err
}
frame.SetTS32(absTS)
if err = writer.NextVideo(); err != nil {
return err
}
} else {
reader.Skip(ds)
}
case FLV_TAG_TYPE_SCRIPT:
r := frame.NewReader()
amf := &rtmp.AMF{
Buffer: util.Buffer(r.ToBytes()),
var amf rtmp.AMF = allocator.Borrow(ds)
_, err = reader.Read(amf)
if err != nil {
return err
}
var obj any
obj, err = amf.Unmarshal()
name := obj
obj, err = amf.Unmarshal()
metaData := obj
frame.Recycle()
var name, metaData any
name, err = amf.Unmarshal()
if err != nil {
return err
}
metaData, err = amf.Unmarshal()
if err != nil {
return err
}

View File

@@ -52,6 +52,7 @@ func (p *RecordReader) Run() (err error) {
return pkg.ErrDisabled
}
allocator := util.NewScalableMemoryAllocator(1 << 10)
writer := m7s.NewPublisherWriter[*rtmp.AudioFrame, *rtmp.VideoFrame](publisher, allocator)
var tagHeader [11]byte
var ts int64
var realTime time.Time
@@ -87,7 +88,8 @@ func (p *RecordReader) Run() (err error) {
}
var flvHead [3]byte
var version, flag byte
err = head.NewReader().ReadByteTo(&flvHead[0], &flvHead[1], &flvHead[2], &version, &flag)
r := head.NewReader()
err = r.ReadByteTo(&flvHead[0], &flvHead[1], &flvHead[2], &version, &flag)
hasAudio := (flag & 0x04) != 0
hasVideo := (flag & 0x01) != 0
if err != nil {
@@ -136,26 +138,38 @@ func (p *RecordReader) Run() (err error) {
dataSize := int(tagHeader[1])<<16 | int(tagHeader[2])<<8 | int(tagHeader[3]) // data size (3 bytes)
timestamp := uint32(tagHeader[4])<<16 | uint32(tagHeader[5])<<8 | uint32(tagHeader[6]) | uint32(tagHeader[7])<<24
// stream id is tagHeader[8:11] (3 bytes), always 0
var frame rtmp.RTMPData
frame.SetAllocator(allocator)
if err = p.reader.ReadNto(dataSize, frame.NextN(dataSize)); err != nil {
break
}
ts = int64(timestamp)
if i != 0 || seekPosition == 0 {
ts += seekTsOffset
}
realTime = stream.StartTime.Add(time.Duration(timestamp) * time.Millisecond)
frame.Timestamp = uint32(ts)
switch t {
case FLV_TAG_TYPE_AUDIO:
if publisher.PubAudio {
err = publisher.WriteAudio(frame.WrapAudio())
frame := writer.AudioFrame
err = p.reader.ReadNto(dataSize, frame.NextN(dataSize))
if err != nil {
return err
}
frame.SetTS32(uint32(ts))
if err = writer.NextAudio(); err != nil {
return err
}
} else {
p.reader.Skip(dataSize)
}
case FLV_TAG_TYPE_VIDEO:
if publisher.PubVideo {
err = publisher.WriteVideo(frame.WrapVideo())
frame := writer.VideoFrame
err = p.reader.ReadNto(dataSize, frame.NextN(dataSize))
if err != nil {
return err
}
frame.SetTS32(uint32(ts))
if err = writer.NextVideo(); err != nil {
return err
}
// After processing the first video frame, check if we need to seek
if i == 0 && seekPosition > 0 {
_, err = p.File.Seek(seekPosition, io.SeekStart)
@@ -168,11 +182,8 @@ func (p *RecordReader) Run() (err error) {
}
}
case FLV_TAG_TYPE_SCRIPT:
r := frame.NewReader()
amf := &rtmp.AMF{
Buffer: util.Buffer(r.ToBytes()),
}
frame.Recycle()
buf := allocator.Borrow(dataSize)
amf := rtmp.AMF(buf)
var obj any
if obj, err = amf.Unmarshal(); err != nil {
return

View File

@@ -113,7 +113,7 @@ func writeMetaTag(file *os.File, suber *m7s.Subscriber, filepositions []uint64,
}
}
amf.Marshals("onMetaData", metaData)
offset := amf.Len() + 13 + 15
offset := amf.GetBuffer().Len() + 13 + 15
if keyframesCount := len(filepositions); keyframesCount > 0 {
metaData["filesize"] = uint64(offset) + filepositions[keyframesCount-1]
for i := range filepositions {
@@ -124,7 +124,7 @@ func writeMetaTag(file *os.File, suber *m7s.Subscriber, filepositions []uint64,
"times": times,
}
}
amf.Reset()
amf.GetBuffer().Reset()
marshals := amf.Marshals("onMetaData", metaData)
task := &writeMetaTagTask{
file: file,
@@ -208,14 +208,14 @@ func (r *Recorder) Run() (err error) {
}
if vr := suber.VideoReader; vr != nil {
vr.ResetAbsTime()
seq := vr.Track.SequenceFrame.(*rtmp.RTMPVideo)
seq := vr.Track.ICodecCtx.(pkg.ISequenceCodecCtx[*rtmp.VideoFrame]).GetSequenceFrame()
err = r.writer.WriteTag(FLV_TAG_TYPE_VIDEO, 0, uint32(seq.Size), seq.Buffers...)
offset = int64(seq.Size + 15)
}
if ar := suber.AudioReader; ar != nil {
ar.ResetAbsTime()
if ar.Track.SequenceFrame != nil {
seq := ar.Track.SequenceFrame.(*rtmp.RTMPAudio)
if seqCtx, ok := ar.Track.ICodecCtx.(pkg.ISequenceCodecCtx[*rtmp.AudioFrame]); ok {
seq := seqCtx.GetSequenceFrame()
err = r.writer.WriteTag(FLV_TAG_TYPE_AUDIO, 0, uint32(seq.Size), seq.Buffers...)
offset += int64(seq.Size + 15)
}
@@ -223,20 +223,14 @@ func (r *Recorder) Run() (err error) {
}
}
return m7s.PlayBlock(ctx.Subscriber, func(audio *rtmp.RTMPAudio) (err error) {
if r.Event.StartTime.IsZero() {
err = r.createStream(suber.AudioReader.Value.WriteTime)
if err != nil {
return err
}
}
return m7s.PlayBlock(ctx.Subscriber, func(audio *rtmp.AudioFrame) (err error) {
if suber.VideoReader == nil && !noFragment {
checkFragment(suber.AudioReader.AbsTime, suber.AudioReader.Value.WriteTime)
}
err = r.writer.WriteTag(FLV_TAG_TYPE_AUDIO, suber.AudioReader.AbsTime, uint32(audio.Size), audio.Buffers...)
offset += int64(audio.Size + 15)
return
}, func(video *rtmp.RTMPVideo) (err error) {
}, func(video *rtmp.VideoFrame) (err error) {
if r.Event.StartTime.IsZero() {
err = r.createStream(suber.VideoReader.Value.WriteTime)
if err != nil {