mirror of
https://github.com/kerberos-io/agent.git
synced 2026-09-10 20:26:51 +00:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5bcefd0015 | ||
|
|
5bb9def42d | ||
|
|
ff38ccbadf | ||
|
|
f64e899de9 |
@@ -159,6 +159,19 @@ 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)
|
||||
|
||||
@@ -580,6 +593,19 @@ 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)
|
||||
|
||||
|
||||
@@ -431,7 +431,12 @@ 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, cannot create MP4 file")
|
||||
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
|
||||
}
|
||||
|
||||
// Add final pending samples before closing
|
||||
@@ -554,7 +559,9 @@ func (mp4 *MP4) Close(config *models.Config) {
|
||||
case "H264", "AVC1":
|
||||
init.AddEmptyTrack(videoTimescale, "video", "und")
|
||||
includePS := true
|
||||
err := init.Moov.Traks[0].SetAVCDescriptor("avc1", mp4.SPSNALUs, mp4.PPSNALUs, includePS)
|
||||
spsNALUs, ppsNALUs := normalizeH264ParameterSets(mp4.SPSNALUs, mp4.PPSNALUs)
|
||||
log.Log.Debug("mp4.Close(): AVC parameter sets: SPS=" + formatNaluDebug(spsNALUs) + ", PPS=" + formatNaluDebug(ppsNALUs))
|
||||
err := init.Moov.Traks[0].SetAVCDescriptor("avc1", spsNALUs, ppsNALUs, includePS)
|
||||
if err != nil {
|
||||
log.Log.Error("mp4.Close(): error setting AVC descriptor: " + err.Error())
|
||||
}
|
||||
@@ -573,7 +580,9 @@ func (mp4 *MP4) Close(config *models.Config) {
|
||||
case "H265", "HVC1":
|
||||
init.AddEmptyTrack(videoTimescale, "video", "und")
|
||||
includePS := true
|
||||
err := init.Moov.Traks[0].SetHEVCDescriptor("hvc1", mp4.VPSNALUs, mp4.SPSNALUs, mp4.PPSNALUs, [][]byte{}, includePS)
|
||||
vpsNALUs, spsNALUs, ppsNALUs := normalizeH265ParameterSets(mp4.VPSNALUs, mp4.SPSNALUs, mp4.PPSNALUs)
|
||||
log.Log.Debug("mp4.Close(): HEVC parameter sets: VPS=" + formatNaluDebug(vpsNALUs) + ", SPS=" + formatNaluDebug(spsNALUs) + ", PPS=" + formatNaluDebug(ppsNALUs))
|
||||
err := init.Moov.Traks[0].SetHEVCDescriptor("hvc1", vpsNALUs, spsNALUs, ppsNALUs, [][]byte{}, includePS)
|
||||
if err != nil {
|
||||
log.Log.Error("mp4.Close(): error setting HEVC descriptor: " + err.Error())
|
||||
}
|
||||
@@ -589,8 +598,8 @@ func (mp4 *MP4) Close(config *models.Config) {
|
||||
init.Moov.Traks[0].Mdia.Mdhd.ModificationTime = macTime
|
||||
}
|
||||
|
||||
// Try adding audio track if available
|
||||
if mp4.AudioTrackName == "AAC" || mp4.AudioTrackName == "MP4A" {
|
||||
// Try adding audio track if available and samples were recorded.
|
||||
if (mp4.AudioTrackName == "AAC" || mp4.AudioTrackName == "MP4A") && mp4.AudioTotalDuration > 0 {
|
||||
// Add an audio track to the moov box
|
||||
init.AddEmptyTrack(audioTimescale, "audio", "und")
|
||||
|
||||
@@ -828,6 +837,121 @@ 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
|
||||
}
|
||||
|
||||
// normalizeH264ParameterSets splits Annex B blobs and extracts SPS/PPS NALUs.
|
||||
func normalizeH264ParameterSets(spsIn [][]byte, ppsIn [][]byte) ([][]byte, [][]byte) {
|
||||
all := make([][]byte, 0, len(spsIn)+len(ppsIn))
|
||||
all = append(all, spsIn...)
|
||||
all = append(all, ppsIn...)
|
||||
var spsOut [][]byte
|
||||
var ppsOut [][]byte
|
||||
for _, blob := range all {
|
||||
for _, nalu := range splitParamSetNALUs(blob) {
|
||||
nalu = removeAnnexBStartCode(nalu)
|
||||
if len(nalu) == 0 {
|
||||
continue
|
||||
}
|
||||
typ := nalu[0] & 0x1F
|
||||
switch typ {
|
||||
case 7:
|
||||
spsOut = append(spsOut, nalu)
|
||||
case 8:
|
||||
ppsOut = append(ppsOut, nalu)
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(spsOut) == 0 {
|
||||
spsOut = sanitizeParameterSets(spsIn)
|
||||
}
|
||||
if len(ppsOut) == 0 {
|
||||
ppsOut = sanitizeParameterSets(ppsIn)
|
||||
}
|
||||
return spsOut, ppsOut
|
||||
}
|
||||
|
||||
// normalizeH265ParameterSets splits Annex B blobs and extracts VPS/SPS/PPS NALUs.
|
||||
func normalizeH265ParameterSets(vpsIn [][]byte, spsIn [][]byte, ppsIn [][]byte) ([][]byte, [][]byte, [][]byte) {
|
||||
all := make([][]byte, 0, len(vpsIn)+len(spsIn)+len(ppsIn))
|
||||
all = append(all, vpsIn...)
|
||||
all = append(all, spsIn...)
|
||||
all = append(all, ppsIn...)
|
||||
var vpsOut [][]byte
|
||||
var spsOut [][]byte
|
||||
var ppsOut [][]byte
|
||||
for _, blob := range all {
|
||||
for _, nalu := range splitParamSetNALUs(blob) {
|
||||
nalu = removeAnnexBStartCode(nalu)
|
||||
if len(nalu) == 0 {
|
||||
continue
|
||||
}
|
||||
typ := (nalu[0] >> 1) & 0x3F
|
||||
switch typ {
|
||||
case 32:
|
||||
vpsOut = append(vpsOut, nalu)
|
||||
case 33:
|
||||
spsOut = append(spsOut, nalu)
|
||||
case 34:
|
||||
ppsOut = append(ppsOut, nalu)
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(vpsOut) == 0 {
|
||||
vpsOut = sanitizeParameterSets(vpsIn)
|
||||
}
|
||||
if len(spsOut) == 0 {
|
||||
spsOut = sanitizeParameterSets(spsIn)
|
||||
}
|
||||
if len(ppsOut) == 0 {
|
||||
ppsOut = sanitizeParameterSets(ppsIn)
|
||||
}
|
||||
return vpsOut, spsOut, ppsOut
|
||||
}
|
||||
|
||||
// splitParamSetNALUs splits Annex B parameter set blobs; raw NALUs are returned as-is.
|
||||
func splitParamSetNALUs(blob []byte) [][]byte {
|
||||
if len(blob) == 0 {
|
||||
return nil
|
||||
}
|
||||
if findStartCode(blob, 0) >= 0 {
|
||||
return splitNALUs(blob)
|
||||
}
|
||||
return [][]byte{blob}
|
||||
}
|
||||
|
||||
func formatNaluDebug(nalus [][]byte) string {
|
||||
if len(nalus) == 0 {
|
||||
return "none"
|
||||
}
|
||||
parts := make([]string, 0, len(nalus))
|
||||
for _, nalu := range nalus {
|
||||
if len(nalu) == 0 {
|
||||
parts = append(parts, "len=0")
|
||||
continue
|
||||
}
|
||||
max := 8
|
||||
if len(nalu) < max {
|
||||
max = len(nalu)
|
||||
}
|
||||
parts = append(parts, fmt.Sprintf("len=%d head=%x", len(nalu), nalu[:max]))
|
||||
}
|
||||
return strings.Join(parts, "; ")
|
||||
}
|
||||
|
||||
// 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