diff --git a/plugin/hls/download.go b/plugin/hls/download.go index 50531a4..2333894 100644 --- a/plugin/hls/download.go +++ b/plugin/hls/download.go @@ -65,7 +65,7 @@ func (plugin *HLSPlugin) queryRecordStreams(params *requestParams) ([]m7s.Record var recordStreams []m7s.RecordStream // 首先查询HLS记录 (ts) - query := plugin.DB.Model(&m7s.RecordStream{}).Where("stream_path = ? AND type = ?", params.streamPath, "hls") + query := plugin.DB.Model(&m7s.RecordStream{}).Where("stream_path = ? AND type = ?", params.streamPath, "ts") // 添加时间范围查询条件 if !params.startTime.IsZero() && !params.endTime.IsZero() { @@ -143,7 +143,7 @@ func (plugin *HLSPlugin) hasOnlyMp4Records(fileInfoList []*fileInfo) bool { } for _, info := range fileInfoList { - if info.recordType == "hls" { + if info.recordType == "ts" { return false } } @@ -155,7 +155,7 @@ func (plugin *HLSPlugin) filterTsFiles(fileInfoList []*fileInfo) []*fileInfo { var filteredList []*fileInfo for _, info := range fileInfoList { - if info.recordType == "hls" { + if info.recordType == "ts" { filteredList = append(filteredList, info) } } diff --git a/plugin/hls/pkg/record.go b/plugin/hls/pkg/record.go index 1d329f7..09471b1 100644 --- a/plugin/hls/pkg/record.go +++ b/plugin/hls/pkg/record.go @@ -139,6 +139,7 @@ func (r *Recorder) Run() (err error) { } return }, func(video *mpegts.VideoFrame) (err error) { + r.Event.Duration = uint32(video.Timestamp.Milliseconds() - r.lastTs.Milliseconds()) vr := r.RecordJob.Subscriber.VideoReader if vr.Value.IDR { if err = r.writeSegment(video.Timestamp, vr.Value.WriteTime); err != nil { diff --git a/plugin/mp4/api.go b/plugin/mp4/api.go index c790ac9..b477c63 100644 --- a/plugin/mp4/api.go +++ b/plugin/mp4/api.go @@ -305,34 +305,18 @@ func (p *MP4Plugin) download(w http.ResponseWriter, r *http.Request) { // 首次添加音频轨道 addAudioTrack(track) } else if !bytes.Equal(lastAudioTrack.ExtraData, trackExtraData) { - // 音频编码参数发生变化,检查是否已存在相同参数的轨道 - for _, history := range audioHistory { - if bytes.Equal(history.ExtraData, trackExtraData) { - // 找到相同参数的轨道,重用它 - audioTrack = history.Track - audioTrack.Samplelist = audioHistory[len(audioHistory)-1].Track.Samplelist - return - } - } - // 创建新的音频轨道 - addAudioTrack(track) + // 音频编码参数发生变化,不再创建新轨道,直接重用最后一个轨道 + audioTrack = lastAudioTrack.Track + audioTrack.Samplelist = lastAudioTrack.Track.Samplelist } } else if track.Cid.IsVideo() { if lastVideoTrack == nil { // 首次添加视频轨道 addVideoTrack(track) } else if !bytes.Equal(lastVideoTrack.ExtraData, trackExtraData) { - // 视频编码参数发生变化,检查是否已存在相同参数的轨道 - for _, history := range videoHistory { - if bytes.Equal(history.ExtraData, trackExtraData) { - // 找到相同参数的轨道,重用它 - videoTrack = history.Track - videoTrack.Samplelist = videoHistory[len(videoHistory)-1].Track.Samplelist - return - } - } - // 创建新的视频轨道 - addVideoTrack(track) + // 视频编码参数发生变化,不再创建新轨道,直接重用最后一个轨道 + videoTrack = lastVideoTrack.Track + videoTrack.Samplelist = lastVideoTrack.Track.Samplelist } } } @@ -355,7 +339,7 @@ func (p *MP4Plugin) download(w http.ResponseWriter, r *http.Request) { return } - trackCount := len(demuxer.Tracks) + //trackCount := len(demuxer.Tracks) // 处理轨道信息 if i == 0 || flag == mp4.FLAG_FRAGMENT { @@ -365,14 +349,6 @@ func (p *MP4Plugin) download(w http.ResponseWriter, r *http.Request) { } } - // 检查轨道数量是否发生变化 - if trackCount != len(muxer.Tracks) { - if flag == mp4.FLAG_FRAGMENT { - // 分片模式下重新生成 MOOV box - moov = muxer.MakeMoov() - } - } - // 处理开始时间偏移(仅第一个文件) if i == 0 { startTimestamp := startTime.Sub(stream.StartTime).Milliseconds() @@ -491,6 +467,15 @@ func (p *MP4Plugin) download(w http.ResponseWriter, r *http.Request) { part.Close() } } else { + + // 检查轨道数量是否发生变化 + //if trackCount != len(muxer.Tracks) { + if flag == mp4.FLAG_FRAGMENT { + // 分片模式下重新生成 MOOV box + moov = muxer.MakeMoov() + } + //} + // 分片 MP4 模式:输出分片格式 var children []box.IBox var totalSize uint64 diff --git a/plugin/mp4/pkg/record.go b/plugin/mp4/pkg/record.go index 77fcb5d..ec63767 100644 --- a/plugin/mp4/pkg/record.go +++ b/plugin/mp4/pkg/record.go @@ -14,7 +14,10 @@ import ( "m7s.live/v5/pkg/codec" "m7s.live/v5/pkg/config" "m7s.live/v5/pkg/storage" + "m7s.live/v5/pkg/util" "m7s.live/v5/plugin/mp4/pkg/box" + + "github.com/langhuihui/gomem" ) type WriteTrailerQueueTask struct { @@ -121,8 +124,9 @@ func NewRecorder(conf config.Record) m7s.IRecorder { type Recorder struct { m7s.DefaultRecorder - muxer *Muxer - file storage.File + muxer *Muxer + file storage.File + firstVideoFrame bool // 标记是否是第一个视频帧 } func (r *Recorder) writeTailer(end time.Time) { @@ -176,6 +180,7 @@ func (r *Recorder) createStream(start time.Time) (err error) { r.muxer = NewMuxerWithStreamPath(0, r.Event.StreamPath) } + r.firstVideoFrame = true // 重置第一个视频帧标志 return r.muxer.WriteInitSegment(r.file) } @@ -302,43 +307,98 @@ func (r *Recorder) Run() (err error) { track.ICodecCtx = video.ICodecCtx } } - ctx := video.ICodecCtx.(pkg.IVideoCodecCtx) - if videoTrackCtx, ok := videoTrack.ICodecCtx.(pkg.IVideoCodecCtx); ok && videoTrackCtx != ctx { - width, height := uint32(ctx.Width()), uint32(ctx.Height()) - oldWidth, oldHeight := uint32(videoTrackCtx.Width()), uint32(videoTrackCtx.Height()) - r.Info("ctx changed, restarting recording", - "old", fmt.Sprintf("%dx%d", oldWidth, oldHeight), - "new", fmt.Sprintf("%dx%d", width, height)) - r.writeTailer(sub.VideoReader.Value.WriteTime) - err = r.createStream(sub.VideoReader.Value.WriteTime) - if err != nil { - return nil - } - at, vt = nil, nil - if vr := sub.VideoReader; vr != nil { - vr.ResetAbsTime() - vt = vr.Track - switch video.ICodecCtx.GetBase().(type) { - case *codec.H264Ctx: - track := r.muxer.AddTrack(box.MP4_CODEC_H264) - videoTrack = track - track.ICodecCtx = video.ICodecCtx - case *codec.H265Ctx: - track := r.muxer.AddTrack(box.MP4_CODEC_H265) - videoTrack = track - track.ICodecCtx = video.ICodecCtx - } - } - if ar := sub.AudioReader; ar != nil { - ar.ResetAbsTime() - } - } + //ctx := video.ICodecCtx.(pkg.IVideoCodecCtx) + //if videoTrackCtx, ok := videoTrack.ICodecCtx.(pkg.IVideoCodecCtx); ok && videoTrackCtx != ctx { + // width, height := uint32(ctx.Width()), uint32(ctx.Height()) + // oldWidth, oldHeight := uint32(videoTrackCtx.Width()), uint32(videoTrackCtx.Height()) + // r.Info("ctx changed, restarting recording", + // "old", fmt.Sprintf("%dx%d", oldWidth, oldHeight), + // "new", fmt.Sprintf("%dx%d", width, height)) + // r.writeTailer(sub.VideoReader.Value.WriteTime) + // err = r.createStream(sub.VideoReader.Value.WriteTime) + // if err != nil { + // return nil + // } + // at, vt = nil, nil + // if vr := sub.VideoReader; vr != nil { + // vr.ResetAbsTime() + // vt = vr.Track + // switch video.ICodecCtx.GetBase().(type) { + // case *codec.H264Ctx: + // track := r.muxer.AddTrack(box.MP4_CODEC_H264) + // videoTrack = track + // track.ICodecCtx = video.ICodecCtx + // case *codec.H265Ctx: + // track := r.muxer.AddTrack(box.MP4_CODEC_H265) + // videoTrack = track + // track.ICodecCtx = video.ICodecCtx + // } + // } + // if ar := sub.AudioReader; ar != nil { + // ar.ResetAbsTime() + // } + //} sample := box.Sample{ Timestamp: sub.VideoReader.AbsTime, KeyFrame: video.IDR, CTS: video.GetCTS32(), Memory: video.Memory, } + // 如果是第一个视频 I 帧,将参数集放在 I 帧前面一起写入 + //if r.firstVideoFrame && video.IDR { + if video.IDR { + // 创建包含参数集的 Memory + var combinedMemory gomem.Memory + var naluSizeLen int = 4 + var sps, pps, vps []byte + + switch ctx := video.ICodecCtx.GetBase().(type) { + case *codec.H264Ctx: + naluSizeLen = int(ctx.RecordInfo.LengthSizeMinusOne) + 1 + sps = ctx.SPS() + pps = ctx.PPS() + if len(sps) > 0 && len(pps) > 0 { + // 写入 SPS + sizeBuf := make([]byte, naluSizeLen) + util.PutBE(sizeBuf, uint32(len(sps))) + combinedMemory.Push(sizeBuf) + combinedMemory.Push(sps) + // 写入 PPS + sizeBuf = make([]byte, naluSizeLen) + util.PutBE(sizeBuf, uint32(len(pps))) + combinedMemory.Push(sizeBuf) + combinedMemory.Push(pps) + } + case *codec.H265Ctx: + naluSizeLen = int(ctx.RecordInfo.LengthSizeMinusOne) + 1 + vps = ctx.VPS() + sps = ctx.SPS() + pps = ctx.PPS() + if len(vps) > 0 && len(sps) > 0 && len(pps) > 0 { + // 写入 VPS + sizeBuf := make([]byte, naluSizeLen) + util.PutBE(sizeBuf, uint32(len(vps))) + combinedMemory.Push(sizeBuf) + combinedMemory.Push(vps) + // 写入 SPS + sizeBuf = make([]byte, naluSizeLen) + util.PutBE(sizeBuf, uint32(len(sps))) + combinedMemory.Push(sizeBuf) + combinedMemory.Push(sps) + // 写入 PPS + sizeBuf = make([]byte, naluSizeLen) + util.PutBE(sizeBuf, uint32(len(pps))) + combinedMemory.Push(sizeBuf) + combinedMemory.Push(pps) + } + } + // 将原始视频帧数据追加到参数集后面 + combinedMemory.Push(video.Memory.Buffers...) + sample.Memory = combinedMemory + r.firstVideoFrame = false + } else if r.firstVideoFrame { + r.firstVideoFrame = false + } return r.muxer.WriteSample(r.file, videoTrack, sample) }) }