mirror of
https://github.com/kerberos-io/agent.git
synced 2026-11-02 19:53:14 +00:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
baaa558cff |
+1
-2
@@ -14,5 +14,4 @@ machinery/test*
|
||||
machinery/init-dev.sh
|
||||
machinery/.env.local
|
||||
machinery/vendor
|
||||
deployments/docker/private-docker-compose.yaml
|
||||
video.mp4
|
||||
deployments/docker/private-docker-compose.yaml
|
||||
@@ -695,37 +695,14 @@ func (g *Golibrtsp) Start(ctx context.Context, streamType string, queue *packets
|
||||
g.Streams[g.VideoH264Index].FPS = fps
|
||||
log.Log.Debug(fmt.Sprintf("capture.golibrtsp.Start(%s): Final FPS=%.2f", streamType, fps))
|
||||
g.VideoH264Forma.SPS = nalu
|
||||
if streamType == "main" && len(nalu) > 0 {
|
||||
// Fallback: store SPS from in-band NALUs when SDP was missing it.
|
||||
configuration.Config.Capture.IPCamera.SPSNALUs = [][]byte{nalu}
|
||||
}
|
||||
|
||||
}
|
||||
case h264.NALUTypePPS:
|
||||
g.VideoH264Forma.PPS = nalu
|
||||
if streamType == "main" && len(nalu) > 0 {
|
||||
// Fallback: store PPS from in-band NALUs when SDP was missing it.
|
||||
configuration.Config.Capture.IPCamera.PPSNALUs = [][]byte{nalu}
|
||||
}
|
||||
}
|
||||
filteredAU = append(filteredAU, nalu)
|
||||
}
|
||||
|
||||
if idrPresent && streamType == "main" {
|
||||
// Ensure config has parameter sets before recordings start.
|
||||
if len(configuration.Config.Capture.IPCamera.SPSNALUs) == 0 && len(g.VideoH264Forma.SPS) > 0 {
|
||||
configuration.Config.Capture.IPCamera.SPSNALUs = [][]byte{g.VideoH264Forma.SPS}
|
||||
log.Log.Warning("capture.golibrtsp.Start(main): fallback SPS set from keyframe")
|
||||
}
|
||||
if len(configuration.Config.Capture.IPCamera.PPSNALUs) == 0 && len(g.VideoH264Forma.PPS) > 0 {
|
||||
configuration.Config.Capture.IPCamera.PPSNALUs = [][]byte{g.VideoH264Forma.PPS}
|
||||
log.Log.Warning("capture.golibrtsp.Start(main): fallback PPS set from keyframe")
|
||||
}
|
||||
if len(configuration.Config.Capture.IPCamera.SPSNALUs) == 0 || len(configuration.Config.Capture.IPCamera.PPSNALUs) == 0 {
|
||||
log.Log.Warning("capture.golibrtsp.Start(main): SPS/PPS still missing after IDR keyframe")
|
||||
}
|
||||
}
|
||||
|
||||
if len(filteredAU) <= 1 || (!nonIDRPresent && !idrPresent) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -159,19 +159,6 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat
|
||||
}
|
||||
|
||||
// Close mp4
|
||||
if len(mp4Video.SPSNALUs) == 0 && len(configuration.Config.Capture.IPCamera.SPSNALUs) > 0 {
|
||||
mp4Video.SPSNALUs = configuration.Config.Capture.IPCamera.SPSNALUs
|
||||
}
|
||||
if len(mp4Video.PPSNALUs) == 0 && len(configuration.Config.Capture.IPCamera.PPSNALUs) > 0 {
|
||||
mp4Video.PPSNALUs = configuration.Config.Capture.IPCamera.PPSNALUs
|
||||
}
|
||||
if len(mp4Video.VPSNALUs) == 0 && len(configuration.Config.Capture.IPCamera.VPSNALUs) > 0 {
|
||||
mp4Video.VPSNALUs = configuration.Config.Capture.IPCamera.VPSNALUs
|
||||
}
|
||||
if (videoCodec == "H264" && (len(mp4Video.SPSNALUs) == 0 || len(mp4Video.PPSNALUs) == 0)) ||
|
||||
(videoCodec == "H265" && (len(mp4Video.VPSNALUs) == 0 || len(mp4Video.SPSNALUs) == 0 || len(mp4Video.PPSNALUs) == 0)) {
|
||||
log.Log.Warning("capture.main.HandleRecordStream(continuous): closing MP4 without full parameter sets, moov may be incomplete")
|
||||
}
|
||||
mp4Video.Close(&config)
|
||||
log.Log.Info("capture.main.HandleRecordStream(continuous): recording finished: file save: " + name)
|
||||
|
||||
@@ -292,9 +279,6 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat
|
||||
ppsNALUS := configuration.Config.Capture.IPCamera.PPSNALUs
|
||||
vpsNALUS := configuration.Config.Capture.IPCamera.VPSNALUs
|
||||
|
||||
if len(spsNALUS) == 0 || len(ppsNALUS) == 0 {
|
||||
log.Log.Warning("capture.main.HandleRecordStream(continuous): missing SPS/PPS at recording start")
|
||||
}
|
||||
// Create a video file, and set the dimensions.
|
||||
mp4Video = video.NewMP4(fullName, spsNALUS, ppsNALUS, vpsNALUS, configuration.Config.Capture.MaxLengthRecording)
|
||||
mp4Video.SetWidth(width)
|
||||
@@ -515,9 +499,6 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat
|
||||
ppsNALUS := configuration.Config.Capture.IPCamera.PPSNALUs
|
||||
vpsNALUS := configuration.Config.Capture.IPCamera.VPSNALUs
|
||||
|
||||
if len(spsNALUS) == 0 || len(ppsNALUS) == 0 {
|
||||
log.Log.Warning("capture.main.HandleRecordStream(motiondetection): missing SPS/PPS at recording start")
|
||||
}
|
||||
// Create a video file, and set the dimensions.
|
||||
mp4Video := video.NewMP4(fullName, spsNALUS, ppsNALUS, vpsNALUS, configuration.Config.Capture.MaxLengthRecording)
|
||||
mp4Video.SetWidth(width)
|
||||
@@ -593,19 +574,6 @@ func HandleRecordStream(queue *packets.Queue, configDirectory string, configurat
|
||||
lastRecordingTime = pkt.CurrentTime
|
||||
|
||||
// This will close the recording and write the last packet.
|
||||
if len(mp4Video.SPSNALUs) == 0 && len(configuration.Config.Capture.IPCamera.SPSNALUs) > 0 {
|
||||
mp4Video.SPSNALUs = configuration.Config.Capture.IPCamera.SPSNALUs
|
||||
}
|
||||
if len(mp4Video.PPSNALUs) == 0 && len(configuration.Config.Capture.IPCamera.PPSNALUs) > 0 {
|
||||
mp4Video.PPSNALUs = configuration.Config.Capture.IPCamera.PPSNALUs
|
||||
}
|
||||
if len(mp4Video.VPSNALUs) == 0 && len(configuration.Config.Capture.IPCamera.VPSNALUs) > 0 {
|
||||
mp4Video.VPSNALUs = configuration.Config.Capture.IPCamera.VPSNALUs
|
||||
}
|
||||
if (videoCodec == "H264" && (len(mp4Video.SPSNALUs) == 0 || len(mp4Video.PPSNALUs) == 0)) ||
|
||||
(videoCodec == "H265" && (len(mp4Video.VPSNALUs) == 0 || len(mp4Video.SPSNALUs) == 0 || len(mp4Video.PPSNALUs) == 0)) {
|
||||
log.Log.Warning("capture.main.HandleRecordStream(motiondetection): closing MP4 without full parameter sets, moov may be incomplete")
|
||||
}
|
||||
mp4Video.Close(&config)
|
||||
log.Log.Info("capture.main.HandleRecordStream(motiondetection): file save: " + name)
|
||||
|
||||
|
||||
@@ -158,68 +158,6 @@ func (mp4 *MP4) AddAudioTrack(codec string) uint32 {
|
||||
func (mp4 *MP4) AddMediaSegment(segNr int) {
|
||||
}
|
||||
|
||||
// updateVideoParameterSetsFromAnnexB inspects Annex B data to fill missing SPS/PPS/VPS.
|
||||
func (mp4 *MP4) updateVideoParameterSetsFromAnnexB(data []byte) {
|
||||
if len(data) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
needSPS := len(mp4.SPSNALUs) == 0
|
||||
needPPS := len(mp4.PPSNALUs) == 0
|
||||
needVPS := len(mp4.VPSNALUs) == 0
|
||||
if !(needSPS || needPPS || needVPS) {
|
||||
return
|
||||
}
|
||||
|
||||
for _, nalu := range splitNALUs(data) {
|
||||
nalu = removeAnnexBStartCode(nalu)
|
||||
if len(nalu) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
switch mp4.VideoTrackName {
|
||||
case "H264", "AVC1":
|
||||
nalType := nalu[0] & 0x1F
|
||||
switch nalType {
|
||||
case 7: // SPS
|
||||
if needSPS {
|
||||
mp4.SPSNALUs = [][]byte{nalu}
|
||||
needSPS = false
|
||||
log.Log.Warning("mp4.updateVideoParameterSetsFromAnnexB(): SPS recovered from in-band NALU")
|
||||
}
|
||||
case 8: // PPS
|
||||
if needPPS {
|
||||
mp4.PPSNALUs = [][]byte{nalu}
|
||||
needPPS = false
|
||||
log.Log.Warning("mp4.updateVideoParameterSetsFromAnnexB(): PPS recovered from in-band NALU")
|
||||
}
|
||||
}
|
||||
case "H265", "HVC1":
|
||||
nalType := (nalu[0] >> 1) & 0x3F
|
||||
switch nalType {
|
||||
case 32: // VPS
|
||||
if needVPS {
|
||||
mp4.VPSNALUs = [][]byte{nalu}
|
||||
needVPS = false
|
||||
log.Log.Warning("mp4.updateVideoParameterSetsFromAnnexB(): VPS recovered from in-band NALU")
|
||||
}
|
||||
case 33: // SPS
|
||||
if needSPS {
|
||||
mp4.SPSNALUs = [][]byte{nalu}
|
||||
needSPS = false
|
||||
log.Log.Warning("mp4.updateVideoParameterSetsFromAnnexB(): SPS recovered from in-band NALU")
|
||||
}
|
||||
case 34: // PPS
|
||||
if needPPS {
|
||||
mp4.PPSNALUs = [][]byte{nalu}
|
||||
needPPS = false
|
||||
log.Log.Warning("mp4.updateVideoParameterSetsFromAnnexB(): PPS recovered from in-band NALU")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// flushPendingVideoSample writes the pending video sample to the current fragment.
|
||||
// If nextPTS is provided (non-zero), it calculates duration from the PTS difference.
|
||||
// If nextPTS is 0 (e.g., at Close time), it uses the last known duration.
|
||||
@@ -345,7 +283,6 @@ func (mp4 *MP4) AddSampleToTrack(trackID uint32, isKeyframe bool, data []byte, p
|
||||
if mp4.Start {
|
||||
|
||||
if trackID == uint32(mp4.VideoTrack) {
|
||||
mp4.updateVideoParameterSetsFromAnnexB(data)
|
||||
|
||||
var lengthPrefixed []byte
|
||||
var err error
|
||||
@@ -431,12 +368,7 @@ func (mp4 *MP4) Close(config *models.Config) {
|
||||
mp4.TotalKeyframesReceived, mp4.TotalKeyframesWritten, mp4.SegmentCount, mp4.FragmentKeyframeCount))
|
||||
|
||||
if mp4.VideoTotalDuration == 0 && mp4.AudioTotalDuration == 0 {
|
||||
log.Log.Error("mp4.Close(): no video or audio samples added, removing empty MP4 file")
|
||||
mp4.Writer.Flush()
|
||||
_ = mp4.FileWriter.Sync()
|
||||
_ = mp4.FileWriter.Close()
|
||||
_ = os.Remove(mp4.FileName)
|
||||
return
|
||||
log.Log.Error("mp4.Close(): no video or audio samples added, cannot create MP4 file")
|
||||
}
|
||||
|
||||
// Add final pending samples before closing
|
||||
@@ -559,11 +491,8 @@ func (mp4 *MP4) Close(config *models.Config) {
|
||||
case "H264", "AVC1":
|
||||
init.AddEmptyTrack(videoTimescale, "video", "und")
|
||||
includePS := true
|
||||
spsNALUs := sanitizeParameterSets(mp4.SPSNALUs)
|
||||
ppsNALUs := sanitizeParameterSets(mp4.PPSNALUs)
|
||||
err := init.Moov.Traks[0].SetAVCDescriptor("avc1", spsNALUs, ppsNALUs, includePS)
|
||||
err := init.Moov.Traks[0].SetAVCDescriptor("avc1", mp4.SPSNALUs, mp4.PPSNALUs, includePS)
|
||||
if err != nil {
|
||||
log.Log.Error("mp4.Close(): error setting AVC descriptor: " + err.Error())
|
||||
}
|
||||
init.Moov.Traks[0].Tkhd.Duration = actualVideoDuration
|
||||
init.Moov.Traks[0].Tkhd.Width = mp4ff.Fixed32(uint32(mp4.width) << 16)
|
||||
@@ -580,12 +509,8 @@ func (mp4 *MP4) Close(config *models.Config) {
|
||||
case "H265", "HVC1":
|
||||
init.AddEmptyTrack(videoTimescale, "video", "und")
|
||||
includePS := true
|
||||
vpsNALUs := sanitizeParameterSets(mp4.VPSNALUs)
|
||||
spsNALUs := sanitizeParameterSets(mp4.SPSNALUs)
|
||||
ppsNALUs := sanitizeParameterSets(mp4.PPSNALUs)
|
||||
err := init.Moov.Traks[0].SetHEVCDescriptor("hvc1", vpsNALUs, spsNALUs, ppsNALUs, [][]byte{}, includePS)
|
||||
err := init.Moov.Traks[0].SetHEVCDescriptor("hvc1", mp4.VPSNALUs, mp4.SPSNALUs, mp4.PPSNALUs, [][]byte{}, includePS)
|
||||
if err != nil {
|
||||
log.Log.Error("mp4.Close(): error setting HEVC descriptor: " + err.Error())
|
||||
}
|
||||
init.Moov.Traks[0].Tkhd.Duration = actualVideoDuration
|
||||
init.Moov.Traks[0].Tkhd.Width = mp4ff.Fixed32(uint32(mp4.width) << 16)
|
||||
@@ -599,8 +524,8 @@ func (mp4 *MP4) Close(config *models.Config) {
|
||||
init.Moov.Traks[0].Mdia.Mdhd.ModificationTime = macTime
|
||||
}
|
||||
|
||||
// Try adding audio track if available and samples were recorded.
|
||||
if (mp4.AudioTrackName == "AAC" || mp4.AudioTrackName == "MP4A") && mp4.AudioTotalDuration > 0 {
|
||||
// Try adding audio track if available
|
||||
if mp4.AudioTrackName == "AAC" || mp4.AudioTrackName == "MP4A" {
|
||||
// Add an audio track to the moov box
|
||||
init.AddEmptyTrack(audioTimescale, "audio", "und")
|
||||
|
||||
@@ -838,22 +763,6 @@ func removeAnnexBStartCode(nalu []byte) []byte {
|
||||
return nalu
|
||||
}
|
||||
|
||||
// sanitizeParameterSets removes Annex B start codes and drops empty NALUs.
|
||||
func sanitizeParameterSets(nalus [][]byte) [][]byte {
|
||||
if len(nalus) == 0 {
|
||||
return nalus
|
||||
}
|
||||
clean := make([][]byte, 0, len(nalus))
|
||||
for _, nalu := range nalus {
|
||||
trimmed := removeAnnexBStartCode(nalu)
|
||||
if len(trimmed) == 0 {
|
||||
continue
|
||||
}
|
||||
clean = append(clean, trimmed)
|
||||
}
|
||||
return clean
|
||||
}
|
||||
|
||||
// splitNALUs splits Annex B data into raw NAL units without start codes.
|
||||
func splitNALUs(data []byte) [][]byte {
|
||||
var nalus [][]byte
|
||||
|
||||
Reference in New Issue
Block a user