mirror of
https://github.com/langhuihui/monibuca.git
synced 2025-12-24 13:48:04 +08:00
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:
@@ -2,11 +2,19 @@ package plugin_flv
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gobwas/ws"
|
||||
"google.golang.org/protobuf/types/known/emptypb"
|
||||
m7s "m7s.live/v5"
|
||||
"m7s.live/v5/pb"
|
||||
"m7s.live/v5/pkg/util"
|
||||
flvpb "m7s.live/v5/plugin/flv/pb"
|
||||
rtmp "m7s.live/v5/plugin/rtmp/pkg"
|
||||
)
|
||||
|
||||
func (p *FLVPlugin) List(ctx context.Context, req *flvpb.ReqRecordList) (resp *pb.RecordResponseList, err error) {
|
||||
@@ -85,3 +93,59 @@ func (plugin *FLVPlugin) Download_(w http.ResponseWriter, r *http.Request) {
|
||||
plugin.processFlvFiles(w, r, flvFileList, params)
|
||||
}
|
||||
}
|
||||
|
||||
func (plugin *FLVPlugin) RegisterHandler() map[string]http.HandlerFunc {
|
||||
return map[string]http.HandlerFunc{
|
||||
"/jessica/{streamPath}": plugin.jessica,
|
||||
}
|
||||
}
|
||||
|
||||
// /jessica/{streamPath}
|
||||
func (plugin *FLVPlugin) jessica(rw http.ResponseWriter, r *http.Request) {
|
||||
subscriber, err := plugin.Subscribe(r.Context(), r.PathValue("streamPath"))
|
||||
defer func() {
|
||||
if err != nil {
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var conn net.Conn
|
||||
conn, err = subscriber.CheckWebSocket(rw, r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if conn == nil {
|
||||
err = errors.New("no websocket connection.")
|
||||
return
|
||||
}
|
||||
var _sendBuffer = net.Buffers{}
|
||||
sendBuffer := _sendBuffer
|
||||
var head [5]byte
|
||||
write := func(typ byte, ts uint32, mem util.Memory) (err error) {
|
||||
head[0] = typ
|
||||
binary.BigEndian.PutUint32(head[1:], ts)
|
||||
err = ws.WriteHeader(conn, ws.Header{
|
||||
Fin: true,
|
||||
OpCode: ws.OpBinary,
|
||||
Length: int64(mem.Size + 5),
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
sendBuffer = append(_sendBuffer, head[:])
|
||||
sendBuffer = append(sendBuffer, mem.Buffers...)
|
||||
if plugin.GetCommonConf().WriteTimeout > 0 {
|
||||
conn.SetWriteDeadline(time.Now().Add(plugin.GetCommonConf().WriteTimeout))
|
||||
}
|
||||
_, err = sendBuffer.WriteTo(conn)
|
||||
return
|
||||
}
|
||||
|
||||
m7s.PlayBlock(subscriber, func(audio *rtmp.AudioFrame) (err error) {
|
||||
return write(1, audio.GetTS32(), audio.Memory)
|
||||
}, func(video *rtmp.VideoFrame) (err error) {
|
||||
return write(2, video.GetTS32(), video.Memory)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"time"
|
||||
|
||||
m7s "m7s.live/v5"
|
||||
"m7s.live/v5/pkg/codec"
|
||||
"m7s.live/v5/pkg/util"
|
||||
flv "m7s.live/v5/plugin/flv/pkg"
|
||||
mp4 "m7s.live/v5/plugin/mp4/pkg"
|
||||
@@ -197,7 +198,7 @@ func (plugin *FLVPlugin) processMp4ToFlv(w http.ResponseWriter, r *http.Request,
|
||||
}
|
||||
|
||||
// 创建DemuxerConverterRange进行MP4解复用和转换
|
||||
demuxer := &mp4.DemuxerConverterRange[*rtmp.RTMPAudio, *rtmp.RTMPVideo]{
|
||||
demuxer := &mp4.DemuxerConverterRange[*rtmp.AudioFrame, *rtmp.VideoFrame]{
|
||||
DemuxerRange: mp4.DemuxerRange{
|
||||
StartTime: params.startTime,
|
||||
EndTime: params.endTime,
|
||||
@@ -208,41 +209,29 @@ func (plugin *FLVPlugin) processMp4ToFlv(w http.ResponseWriter, r *http.Request,
|
||||
|
||||
// 创建FLV编码器状态
|
||||
flvWriter := flv.NewFlvWriter(w)
|
||||
hasWritten := false
|
||||
ts := int64(0) // 初始化时间戳
|
||||
tsOffset := int64(0) // 偏移时间戳
|
||||
demuxer.OnCodec = func(a, v codec.ICodecCtx) {
|
||||
flvWriter.WriteHeader(a != nil, v != nil)
|
||||
}
|
||||
demuxer.OnAudio = func(audio *rtmp.AudioFrame) error {
|
||||
// 计算调整后的时间戳
|
||||
ts = int64(audio.Timestamp) + tsOffset
|
||||
timestamp := uint32(ts)
|
||||
// 写入音频数据帧
|
||||
return flvWriter.WriteTag(flv.FLV_TAG_TYPE_AUDIO, timestamp, uint32(audio.Size), audio.Buffers...)
|
||||
}
|
||||
demuxer.OnVideo = func(frame *rtmp.VideoFrame) error {
|
||||
// 计算调整后的时间戳
|
||||
ts = int64(frame.Timestamp) + tsOffset
|
||||
timestamp := uint32(ts)
|
||||
// 写入视频数据帧
|
||||
return flvWriter.WriteTag(flv.FLV_TAG_TYPE_VIDEO, timestamp, uint32(frame.Size), frame.Buffers...)
|
||||
}
|
||||
// 执行解复用和转换
|
||||
err := demuxer.Demux(r.Context(),
|
||||
func(audio *rtmp.RTMPAudio) error {
|
||||
if !hasWritten {
|
||||
if err := flvWriter.WriteHeader(demuxer.AudioTrack != nil, demuxer.VideoTrack != nil); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// 计算调整后的时间戳
|
||||
ts = int64(audio.Timestamp) + tsOffset
|
||||
timestamp := uint32(ts)
|
||||
|
||||
// 写入音频数据帧
|
||||
return flvWriter.WriteTag(flv.FLV_TAG_TYPE_AUDIO, timestamp, uint32(audio.Size), audio.Buffers...)
|
||||
}, func(frame *rtmp.RTMPVideo) error {
|
||||
if !hasWritten {
|
||||
if err := flvWriter.WriteHeader(demuxer.AudioTrack != nil, demuxer.VideoTrack != nil); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// 计算调整后的时间戳
|
||||
ts = int64(frame.Timestamp) + tsOffset
|
||||
timestamp := uint32(ts)
|
||||
// 写入视频数据帧
|
||||
return flvWriter.WriteTag(flv.FLV_TAG_TYPE_VIDEO, timestamp, uint32(frame.Size), frame.Buffers...)
|
||||
})
|
||||
err := demuxer.Demux(r.Context())
|
||||
if err != nil {
|
||||
plugin.Error("MP4 to FLV conversion failed", "err", err)
|
||||
if !hasWritten {
|
||||
http.Error(w, "Conversion failed", http.StatusInternalServerError)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -268,7 +257,7 @@ func (plugin *FLVPlugin) processFlvFiles(w http.ResponseWriter, r *http.Request,
|
||||
startOffsetTime = fileInfoList[0].startOffsetTime
|
||||
}
|
||||
|
||||
var amf *rtmp.AMF
|
||||
var amf rtmp.AMF
|
||||
var metaData rtmp.EcmaArray
|
||||
initMetaData := func(reader io.Reader, dataLen uint32) {
|
||||
data := make([]byte, dataLen+4)
|
||||
@@ -276,9 +265,7 @@ func (plugin *FLVPlugin) processFlvFiles(w http.ResponseWriter, r *http.Request,
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
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()
|
||||
if err == nil {
|
||||
@@ -302,7 +289,7 @@ func (plugin *FLVPlugin) processFlvFiles(w http.ResponseWriter, r *http.Request,
|
||||
"times": times,
|
||||
}
|
||||
amf.Marshals("onMetaData", metaData)
|
||||
offsetDelta := amf.Len() + 15
|
||||
offsetDelta := amf.GetBuffer().Len() + 15
|
||||
offset := offsetDelta + len(flvHead)
|
||||
contentLength += uint64(offset)
|
||||
metaData["duration"] = params.timeRange.Seconds()
|
||||
@@ -314,7 +301,7 @@ func (plugin *FLVPlugin) processFlvFiles(w http.ResponseWriter, r *http.Request,
|
||||
"filepositions": filepositions,
|
||||
"times": times,
|
||||
}
|
||||
amf.Reset()
|
||||
amf.GetBuffer().Reset()
|
||||
amf.Marshals("onMetaData", metaData)
|
||||
plugin.Info("start download", "metaData", metaData)
|
||||
w.Header().Set("Content-Length", strconv.FormatInt(int64(contentLength), 10))
|
||||
@@ -361,13 +348,13 @@ func (plugin *FLVPlugin) processFlvFiles(w http.ResponseWriter, r *http.Request,
|
||||
return
|
||||
}
|
||||
tagHead[0] = flv.FLV_TAG_TYPE_SCRIPT
|
||||
l := amf.Len()
|
||||
l := amf.GetBuffer().Len()
|
||||
tagHead[1] = byte(l >> 16)
|
||||
tagHead[2] = byte(l >> 8)
|
||||
tagHead[3] = byte(l)
|
||||
flv.PutFlvTimestamp(tagHead, 0)
|
||||
writer.Write(tagHead)
|
||||
writer.Write(amf.Buffer)
|
||||
writer.Write([]byte(amf))
|
||||
l += 11
|
||||
binary.BigEndian.PutUint32(tagHead[:4], uint32(l))
|
||||
writer.Write(tagHead[:4])
|
||||
|
||||
@@ -5,10 +5,7 @@ import (
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gobwas/ws"
|
||||
"github.com/gobwas/ws/wsutil"
|
||||
m7s "m7s.live/v5"
|
||||
"m7s.live/v5/pkg/util"
|
||||
"m7s.live/v5/plugin/flv/pb"
|
||||
@@ -33,7 +30,7 @@ var _ = m7s.InstallPlugin[FLVPlugin](m7s.PluginMeta{
|
||||
NewPullProxy: m7s.NewHTTPPullPorxy,
|
||||
})
|
||||
|
||||
func (plugin *FLVPlugin) OnInit() (err error) {
|
||||
func (plugin *FLVPlugin) Start() (err error) {
|
||||
_, port, _ := strings.Cut(plugin.GetCommonConf().HTTP.ListenAddr, ":")
|
||||
if port == "80" {
|
||||
plugin.PlayAddr = append(plugin.PlayAddr, "http://{hostName}/flv/{streamPath}", "ws://{hostName}/flv/{streamPath}")
|
||||
@@ -57,7 +54,6 @@ func (plugin *FLVPlugin) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
}
|
||||
}()
|
||||
var conn net.Conn
|
||||
var live Live
|
||||
if r.URL.RawQuery != "" {
|
||||
streamPath += "?" + r.URL.RawQuery
|
||||
@@ -67,39 +63,21 @@ func (plugin *FLVPlugin) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
live.Subscriber.RemoteAddr = r.RemoteAddr
|
||||
conn, err = live.Subscriber.CheckWebSocket(w, r)
|
||||
|
||||
var ctx util.HTTP_WS_Writer
|
||||
ctx.Conn, err = live.Subscriber.CheckWebSocket(w, r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if conn != nil {
|
||||
live.WriteFlvTag = func(flv net.Buffers) (err error) {
|
||||
return wsutil.WriteServerMessage(conn, ws.OpBinary, util.ConcatBuffers(flv))
|
||||
}
|
||||
err = live.Run()
|
||||
return
|
||||
}
|
||||
wto := plugin.GetCommonConf().WriteTimeout
|
||||
if conn == nil {
|
||||
w.Header().Set("Content-Type", "video/x-flv")
|
||||
w.Header().Set("Transfer-Encoding", "identity")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
if hijacker, ok := w.(http.Hijacker); ok && wto > 0 {
|
||||
conn, _, _ = hijacker.Hijack()
|
||||
conn.SetWriteDeadline(time.Now().Add(wto))
|
||||
}
|
||||
}
|
||||
if conn == nil {
|
||||
live.WriteFlvTag = func(flv net.Buffers) (err error) {
|
||||
_, err = flv.WriteTo(w)
|
||||
return
|
||||
}
|
||||
w.(http.Flusher).Flush()
|
||||
} else {
|
||||
live.WriteFlvTag = func(flv net.Buffers) (err error) {
|
||||
conn.SetWriteDeadline(time.Now().Add(wto))
|
||||
_, err = flv.WriteTo(conn)
|
||||
ctx.WriteTimeout = plugin.GetCommonConf().WriteTimeout
|
||||
ctx.ContentType = "video/x-flv"
|
||||
ctx.ServeHTTP(w, r)
|
||||
live.WriteFlvTag = func(flv net.Buffers) (err error) {
|
||||
_, err = flv.WriteTo(&ctx)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return ctx.Flush()
|
||||
}
|
||||
err = live.Run()
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user