diff --git a/internal/webrtc/keyframe_detector.go b/internal/webrtc/keyframe_detector.go new file mode 100644 index 0000000..42df7ea --- /dev/null +++ b/internal/webrtc/keyframe_detector.go @@ -0,0 +1,26 @@ +package webrtc + +import ( + "github.com/pion/rtp" +) + +const ( + naluTypeBitmask = 0x1F + + idrNALUType = 5 + spsNALUType = 7 + ppsNALUType = 8 +) + +func isKeyframe(pkt *rtp.Packet, codec videoTrackCodec, depacketizer rtp.Depacketizer) bool { + if codec == videoTrackCodecH264 { + nalu, err := depacketizer.Unmarshal(pkt.Payload) + if err != nil || len(nalu) < 6 { + return false + } + + firstNaluType := nalu[4] & naluTypeBitmask + return firstNaluType == idrNALUType || firstNaluType == spsNALUType || firstNaluType == ppsNALUType + } + return false +} diff --git a/internal/webrtc/webrtc.go b/internal/webrtc/webrtc.go index 7204e6a..133354f 100644 --- a/internal/webrtc/webrtc.go +++ b/internal/webrtc/webrtc.go @@ -53,8 +53,9 @@ type ( } videoTrack struct { - rid string - packetsReceived atomic.Uint64 + rid string + packetsReceived atomic.Uint64 + lastKeyFrameSeen atomic.Value } videoTrackCodec int @@ -151,6 +152,7 @@ func addTrack(stream *stream, rid string) (*videoTrack, error) { } t := &videoTrack{rid: rid} + t.lastKeyFrameSeen.Store(time.Time{}) stream.videoTracks = append(stream.videoTracks, t) return t, nil } @@ -371,8 +373,9 @@ func Configure() { } type StreamStatusVideo struct { - RID string `json:"rid"` - PacketsReceived uint64 `json:"packetsReceived"` + RID string `json:"rid"` + PacketsReceived uint64 `json:"packetsReceived"` + LastKeyFrameSeen time.Time `json:"lastKeyFrameSeen"` } type StreamStatus struct { @@ -418,9 +421,15 @@ func GetStreamStatuses() []StreamStatus { streamStatusVideo := []StreamStatusVideo{} for _, videoTrack := range stream.videoTracks { + var lastKeyFrameSeen time.Time + if v, ok := videoTrack.lastKeyFrameSeen.Load().(time.Time); ok { + lastKeyFrameSeen = v + } + streamStatusVideo = append(streamStatusVideo, StreamStatusVideo{ - RID: videoTrack.rid, - PacketsReceived: videoTrack.packetsReceived.Load(), + RID: videoTrack.rid, + PacketsReceived: videoTrack.packetsReceived.Load(), + LastKeyFrameSeen: lastKeyFrameSeen, }) } diff --git a/internal/webrtc/whip.go b/internal/webrtc/whip.go index 22d1d78..d3c3bea 100644 --- a/internal/webrtc/whip.go +++ b/internal/webrtc/whip.go @@ -6,9 +6,11 @@ import ( "log" "math" "strings" + "time" "github.com/pion/rtcp" "github.com/pion/rtp" + "github.com/pion/rtp/codecs" "github.com/pion/webrtc/v4" ) @@ -65,6 +67,16 @@ func videoWriter(remoteTrack *webrtc.TrackRemote, stream *stream, peerConnection rtpPkt := &rtp.Packet{} codec := getVideoTrackCodec(remoteTrack.Codec().RTPCodecCapability.MimeType) + var depacketizer rtp.Depacketizer + switch codec { + case videoTrackCodecH264: + depacketizer = &codecs.H264Packet{} + case videoTrackCodecVP8: + depacketizer = &codecs.VP8Packet{} + case videoTrackCodecVP9: + depacketizer = &codecs.VP9Packet{} + } + lastTimestamp := uint32(0) lastTimestampSet := false @@ -88,6 +100,10 @@ func videoWriter(remoteTrack *webrtc.TrackRemote, stream *stream, peerConnection videoTrack.packetsReceived.Add(1) + if isKeyframe(rtpPkt, codec, depacketizer) { + videoTrack.lastKeyFrameSeen.Store(time.Now()) + } + rtpPkt.Extension = false rtpPkt.Extensions = nil