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
// 首先查询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)
}
}

View File

@@ -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 {

View File

@@ -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

View File

@@ -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)
})
}