fix: download hls recordtype 'ts' not 'hls',mp4 record save pps/sps before idr

This commit is contained in:
pggiroro
2025-12-03 11:07:49 +08:00
parent 2a2b00b862
commit f16fe2996e
4 changed files with 113 additions and 67 deletions

View File

@@ -65,7 +65,7 @@ func (plugin *HLSPlugin) queryRecordStreams(params *requestParams) ([]m7s.Record
var recordStreams []m7s.RecordStream var recordStreams []m7s.RecordStream
// 首先查询HLS记录 (ts) // 首先查询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() { if !params.startTime.IsZero() && !params.endTime.IsZero() {
@@ -143,7 +143,7 @@ func (plugin *HLSPlugin) hasOnlyMp4Records(fileInfoList []*fileInfo) bool {
} }
for _, info := range fileInfoList { for _, info := range fileInfoList {
if info.recordType == "hls" { if info.recordType == "ts" {
return false return false
} }
} }
@@ -155,7 +155,7 @@ func (plugin *HLSPlugin) filterTsFiles(fileInfoList []*fileInfo) []*fileInfo {
var filteredList []*fileInfo var filteredList []*fileInfo
for _, info := range fileInfoList { for _, info := range fileInfoList {
if info.recordType == "hls" { if info.recordType == "ts" {
filteredList = append(filteredList, info) filteredList = append(filteredList, info)
} }
} }

View File

@@ -139,6 +139,7 @@ func (r *Recorder) Run() (err error) {
} }
return return
}, func(video *mpegts.VideoFrame) (err error) { }, func(video *mpegts.VideoFrame) (err error) {
r.Event.Duration = uint32(video.Timestamp.Milliseconds() - r.lastTs.Milliseconds())
vr := r.RecordJob.Subscriber.VideoReader vr := r.RecordJob.Subscriber.VideoReader
if vr.Value.IDR { if vr.Value.IDR {
if err = r.writeSegment(video.Timestamp, vr.Value.WriteTime); err != nil { if err = r.writeSegment(video.Timestamp, vr.Value.WriteTime); err != nil {

View File

@@ -305,34 +305,18 @@ func (p *MP4Plugin) download(w http.ResponseWriter, r *http.Request) {
// 首次添加音频轨道 // 首次添加音频轨道
addAudioTrack(track) addAudioTrack(track)
} else if !bytes.Equal(lastAudioTrack.ExtraData, trackExtraData) { } else if !bytes.Equal(lastAudioTrack.ExtraData, trackExtraData) {
// 音频编码参数发生变化,检查是否已存在相同参数的轨道 // 音频编码参数发生变化,不再创建新轨道,直接重用最后一个轨道
for _, history := range audioHistory { audioTrack = lastAudioTrack.Track
if bytes.Equal(history.ExtraData, trackExtraData) { audioTrack.Samplelist = lastAudioTrack.Track.Samplelist
// 找到相同参数的轨道,重用它
audioTrack = history.Track
audioTrack.Samplelist = audioHistory[len(audioHistory)-1].Track.Samplelist
return
}
}
// 创建新的音频轨道
addAudioTrack(track)
} }
} else if track.Cid.IsVideo() { } else if track.Cid.IsVideo() {
if lastVideoTrack == nil { if lastVideoTrack == nil {
// 首次添加视频轨道 // 首次添加视频轨道
addVideoTrack(track) addVideoTrack(track)
} else if !bytes.Equal(lastVideoTrack.ExtraData, trackExtraData) { } else if !bytes.Equal(lastVideoTrack.ExtraData, trackExtraData) {
// 视频编码参数发生变化,检查是否已存在相同参数的轨道 // 视频编码参数发生变化,不再创建新轨道,直接重用最后一个轨道
for _, history := range videoHistory { videoTrack = lastVideoTrack.Track
if bytes.Equal(history.ExtraData, trackExtraData) { videoTrack.Samplelist = lastVideoTrack.Track.Samplelist
// 找到相同参数的轨道,重用它
videoTrack = history.Track
videoTrack.Samplelist = videoHistory[len(videoHistory)-1].Track.Samplelist
return
}
}
// 创建新的视频轨道
addVideoTrack(track)
} }
} }
} }
@@ -355,7 +339,7 @@ func (p *MP4Plugin) download(w http.ResponseWriter, r *http.Request) {
return return
} }
trackCount := len(demuxer.Tracks) //trackCount := len(demuxer.Tracks)
// 处理轨道信息 // 处理轨道信息
if i == 0 || flag == mp4.FLAG_FRAGMENT { 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 { if i == 0 {
startTimestamp := startTime.Sub(stream.StartTime).Milliseconds() startTimestamp := startTime.Sub(stream.StartTime).Milliseconds()
@@ -491,6 +467,15 @@ func (p *MP4Plugin) download(w http.ResponseWriter, r *http.Request) {
part.Close() part.Close()
} }
} else { } else {
// 检查轨道数量是否发生变化
//if trackCount != len(muxer.Tracks) {
if flag == mp4.FLAG_FRAGMENT {
// 分片模式下重新生成 MOOV box
moov = muxer.MakeMoov()
}
//}
// 分片 MP4 模式:输出分片格式 // 分片 MP4 模式:输出分片格式
var children []box.IBox var children []box.IBox
var totalSize uint64 var totalSize uint64

View File

@@ -14,7 +14,10 @@ import (
"m7s.live/v5/pkg/codec" "m7s.live/v5/pkg/codec"
"m7s.live/v5/pkg/config" "m7s.live/v5/pkg/config"
"m7s.live/v5/pkg/storage" "m7s.live/v5/pkg/storage"
"m7s.live/v5/pkg/util"
"m7s.live/v5/plugin/mp4/pkg/box" "m7s.live/v5/plugin/mp4/pkg/box"
"github.com/langhuihui/gomem"
) )
type WriteTrailerQueueTask struct { type WriteTrailerQueueTask struct {
@@ -121,8 +124,9 @@ func NewRecorder(conf config.Record) m7s.IRecorder {
type Recorder struct { type Recorder struct {
m7s.DefaultRecorder m7s.DefaultRecorder
muxer *Muxer muxer *Muxer
file storage.File file storage.File
firstVideoFrame bool // 标记是否是第一个视频帧
} }
func (r *Recorder) writeTailer(end time.Time) { 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.muxer = NewMuxerWithStreamPath(0, r.Event.StreamPath)
} }
r.firstVideoFrame = true // 重置第一个视频帧标志
return r.muxer.WriteInitSegment(r.file) return r.muxer.WriteInitSegment(r.file)
} }
@@ -302,43 +307,98 @@ func (r *Recorder) Run() (err error) {
track.ICodecCtx = video.ICodecCtx track.ICodecCtx = video.ICodecCtx
} }
} }
ctx := video.ICodecCtx.(pkg.IVideoCodecCtx) //ctx := video.ICodecCtx.(pkg.IVideoCodecCtx)
if videoTrackCtx, ok := videoTrack.ICodecCtx.(pkg.IVideoCodecCtx); ok && videoTrackCtx != ctx { //if videoTrackCtx, ok := videoTrack.ICodecCtx.(pkg.IVideoCodecCtx); ok && videoTrackCtx != ctx {
width, height := uint32(ctx.Width()), uint32(ctx.Height()) // width, height := uint32(ctx.Width()), uint32(ctx.Height())
oldWidth, oldHeight := uint32(videoTrackCtx.Width()), uint32(videoTrackCtx.Height()) // oldWidth, oldHeight := uint32(videoTrackCtx.Width()), uint32(videoTrackCtx.Height())
r.Info("ctx changed, restarting recording", // r.Info("ctx changed, restarting recording",
"old", fmt.Sprintf("%dx%d", oldWidth, oldHeight), // "old", fmt.Sprintf("%dx%d", oldWidth, oldHeight),
"new", fmt.Sprintf("%dx%d", width, height)) // "new", fmt.Sprintf("%dx%d", width, height))
r.writeTailer(sub.VideoReader.Value.WriteTime) // r.writeTailer(sub.VideoReader.Value.WriteTime)
err = r.createStream(sub.VideoReader.Value.WriteTime) // err = r.createStream(sub.VideoReader.Value.WriteTime)
if err != nil { // if err != nil {
return nil // return nil
} // }
at, vt = nil, nil // at, vt = nil, nil
if vr := sub.VideoReader; vr != nil { // if vr := sub.VideoReader; vr != nil {
vr.ResetAbsTime() // vr.ResetAbsTime()
vt = vr.Track // vt = vr.Track
switch video.ICodecCtx.GetBase().(type) { // switch video.ICodecCtx.GetBase().(type) {
case *codec.H264Ctx: // case *codec.H264Ctx:
track := r.muxer.AddTrack(box.MP4_CODEC_H264) // track := r.muxer.AddTrack(box.MP4_CODEC_H264)
videoTrack = track // videoTrack = track
track.ICodecCtx = video.ICodecCtx // track.ICodecCtx = video.ICodecCtx
case *codec.H265Ctx: // case *codec.H265Ctx:
track := r.muxer.AddTrack(box.MP4_CODEC_H265) // track := r.muxer.AddTrack(box.MP4_CODEC_H265)
videoTrack = track // videoTrack = track
track.ICodecCtx = video.ICodecCtx // track.ICodecCtx = video.ICodecCtx
} // }
} // }
if ar := sub.AudioReader; ar != nil { // if ar := sub.AudioReader; ar != nil {
ar.ResetAbsTime() // ar.ResetAbsTime()
} // }
} //}
sample := box.Sample{ sample := box.Sample{
Timestamp: sub.VideoReader.AbsTime, Timestamp: sub.VideoReader.AbsTime,
KeyFrame: video.IDR, KeyFrame: video.IDR,
CTS: video.GetCTS32(), CTS: video.GetCTS32(),
Memory: video.Memory, 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) return r.muxer.WriteSample(r.file, videoTrack, sample)
}) })
} }