mirror of
https://github.com/livekit/livekit.git
synced 2026-07-09 23:12:19 +00:00
rename GOP to video frame cache group.
This commit is contained in:
@@ -210,9 +210,9 @@ type BufferBase struct {
|
||||
videoFrameCacheEnabled bool
|
||||
videoFrameCacheMaxDuration time.Duration // optional bound on key-frame interval (0 = bucket-bounded only)
|
||||
videoFrameCacheHasKeyFrame bool
|
||||
videoFrameCacheKeyFrameESN uint64 // ext sequence number of the current GOP's first key-frame packet
|
||||
videoFrameCacheKeyFrameETS uint64 // ext timestamp of the current GOP's key frame
|
||||
videoFrameCacheLatestTS uint64 // maximum ext timestamp seen in the current GOP (resets on a new key frame)
|
||||
videoFrameCacheKeyFrameESN uint64 // ext sequence number of the current video frame cache group's first key-frame packet
|
||||
videoFrameCacheKeyFrameETS uint64 // ext timestamp of the current video frame cache group's key frame
|
||||
videoFrameCacheLatestTS uint64 // maximum ext timestamp seen in the current video frame cache group (resets on a new key frame)
|
||||
|
||||
isPaused bool
|
||||
frameRateCalculator [DefaultMaxLayerSpatial + 1]FrameRateCalculator
|
||||
@@ -555,7 +555,7 @@ func (b *BufferBase) restartStreamLocked(reason string, isDetected bool) {
|
||||
b.StopKeyFrameSeeder()
|
||||
b.stopRTPStats("stream-restart")
|
||||
b.flushExtPacketsLocked()
|
||||
// the marked GOP references the pre-restart sequence-number base / evicted bucket contents
|
||||
// the marked video frame cache group references the pre-restart sequence-number base / evicted bucket contents
|
||||
b.videoFrameCacheHasKeyFrame = false
|
||||
|
||||
// restart
|
||||
@@ -664,7 +664,7 @@ func (b *BufferBase) ReadExtended(buf []byte) (*ExtPacket, error) {
|
||||
// packets are copied - only the key-frame boundary is tracked. maxDuration bounds the served
|
||||
// key-frame interval AND drives the retransmit bucket to retain that much history (see
|
||||
// maybeGrowBucket), so the key frame is not evicted before it can be read; <= 0 keeps the default
|
||||
// ~1s retransmit window (and bounds the GOP to it). No-op for audio buffers.
|
||||
// ~1s retransmit window (and bounds the video frame cache group to it). No-op for audio buffers.
|
||||
func (b *BufferBase) EnableVideoFrameCache(maxDuration time.Duration) {
|
||||
b.Lock()
|
||||
defer b.Unlock()
|
||||
@@ -674,15 +674,15 @@ func (b *BufferBase) EnableVideoFrameCache(maxDuration time.Duration) {
|
||||
b.logger.Debugw("video frame cache enabled on buffer", "maxDuration", maxDuration)
|
||||
}
|
||||
|
||||
// markVideoFrameCacheLocked records the key-frame boundary of the current GOP and tracks its span. Caller holds
|
||||
// markVideoFrameCacheLocked records the key-frame boundary of the current video frame cache group and tracks its span. Caller holds
|
||||
// the lock.
|
||||
func (b *BufferBase) markVideoFrameCacheLocked(ep *ExtPacket) {
|
||||
if ep == nil || ep.Packet == nil || len(ep.Packet.Payload) == 0 {
|
||||
return
|
||||
}
|
||||
if ep.IsKeyFrame && (!b.videoFrameCacheHasKeyFrame || ep.ExtTimestamp != b.videoFrameCacheKeyFrameETS) {
|
||||
// a new key frame starts a new GOP; remember its first packet's sequence number and reset the
|
||||
// span to the key frame so a stale packet from the previous GOP cannot stretch it
|
||||
// a new key frame can be used to decoders to reset; remember its first packet's sequence number and reset the
|
||||
// span to the key frame so a stale packet from the previous video frame cache group cannot stretch it
|
||||
b.videoFrameCacheKeyFrameESN = ep.ExtSequenceNumber
|
||||
b.videoFrameCacheKeyFrameETS = ep.ExtTimestamp
|
||||
b.videoFrameCacheLatestTS = ep.ExtTimestamp
|
||||
@@ -690,21 +690,21 @@ func (b *BufferBase) markVideoFrameCacheLocked(ep *ExtPacket) {
|
||||
b.logger.Debugw("video frame cache: marked key frame", "keyFrameSN", b.videoFrameCacheKeyFrameESN, "keyFrameTS", b.videoFrameCacheKeyFrameETS)
|
||||
return
|
||||
}
|
||||
// track the maximum timestamp seen in the current GOP (not the last-written one) so an
|
||||
// track the maximum timestamp seen in the current video frame cache group (not the last-written one) so an
|
||||
// out-of-order, older packet arriving last cannot shrink the measured span and let GetVideoFrameCache serve
|
||||
// more than videoFrameCacheMaxDuration. The head packet's timestamp is always <= videoFrameCacheLatestTS, so the
|
||||
// duration gate in GetVideoFrameCache strictly bounds the served GOP.
|
||||
// duration gate in GetVideoFrameCache strictly bounds the served video frame cache group.
|
||||
if ep.ExtTimestamp > b.videoFrameCacheLatestTS {
|
||||
b.videoFrameCacheLatestTS = ep.ExtTimestamp
|
||||
}
|
||||
}
|
||||
|
||||
// GetVideoFrameCache reads the packets of the current group-of-pictures (from the most recent key frame up to
|
||||
// the latest packet) back from the retransmit bucket, so a newly attached relay / down track can be
|
||||
// the latest packet) back from the retransmit bucket, so a newly attached down track can be
|
||||
// bootstrapped without requesting a fresh key frame (PLI). Returns (nil, false) when the cache is
|
||||
// disabled, no key frame has been marked, the key-frame interval exceeds the configured bound, or
|
||||
// the key frame is no longer retained in the bucket (the GOP length is ultimately bounded by the
|
||||
// bucket capacity). Lost packets within the GOP are skipped.
|
||||
// the key frame is no longer retained in the bucket (the video frame cache group length is ultimately bounded by the
|
||||
// bucket capacity). Lost packets within the video frame cache group are skipped.
|
||||
//
|
||||
// The packets are returned as ExtPackets reconstructed from the bucket bytes so they can be replayed
|
||||
// through the normal forward path (WriteRTP): ExtSequenceNumber comes from the bucket key and
|
||||
@@ -746,7 +746,7 @@ func (b *BufferBase) GetVideoFrameCache() ([]*ExtPacket, bool) {
|
||||
}
|
||||
|
||||
pkts := b.reconstructPacketsLocked(b.videoFrameCacheKeyFrameESN, headESN)
|
||||
// the key frame itself must be present (its first packet), otherwise the GOP cannot be served
|
||||
// the key frame itself must be present (its first packet), otherwise the video frame cache group cannot be served
|
||||
if len(pkts) == 0 || pkts[0].ExtSequenceNumber != b.videoFrameCacheKeyFrameESN {
|
||||
var firstSN uint64
|
||||
if len(pkts) > 0 {
|
||||
@@ -789,7 +789,7 @@ func (b *BufferBase) GetPacketsAfter(afterESN uint64) ([]*ExtPacket, bool) {
|
||||
|
||||
// reconstructPacketsLocked builds self-contained ExtPackets for the sequence-number range
|
||||
// [fromSN, headESN] from the retransmit bucket. Lost packets are skipped. ExtTimestamp is
|
||||
// reconstructed relative to the marked key frame (a GOP spans well under one 32-bit timestamp wrap)
|
||||
// reconstructed relative to the marked key frame (a video frame cache group spans well under one 32-bit timestamp wrap)
|
||||
// and IsKeyFrame flags packets at the key-frame timestamp. The dependency descriptor is not
|
||||
// reconstructed. Caller holds the lock.
|
||||
func (b *BufferBase) reconstructPacketsLocked(fromSN, headESN uint64) []*ExtPacket {
|
||||
@@ -1412,7 +1412,7 @@ func (b *BufferBase) flushExtPacketsLocked() {
|
||||
//
|
||||
// Normally the target is ~1s of packets (the NACK / retransmit window), bounded by maxPkts. When
|
||||
// videoFrameCacheSizing is set (the video frame cache is enabled with a positive duration), the bucket must retain
|
||||
// the whole GOP duration plus ~0.5s margin so the key frame is not evicted before it is at most
|
||||
// the whole video frame cache group duration plus ~0.5s margin so the key frame is not evicted before it is at most
|
||||
// videoFrameCacheMaxDuration old; the cap is raised to fit since the default maxPkts is only ~1s worth.
|
||||
func bucketGrowTarget(pps, maxPkts int, videoFrameCacheSizing bool, videoFrameCacheMaxDuration time.Duration) (targetPkts, effectiveMaxPkts int) {
|
||||
targetPkts = pps
|
||||
@@ -1444,7 +1444,7 @@ func (b *BufferBase) maybeGrowBucket(now int64) {
|
||||
maxPkts = b.params.MaxAudioPkts
|
||||
}
|
||||
|
||||
// when the video frame cache is enabled the bucket must retain the whole configured GOP
|
||||
// when the video frame cache is enabled the bucket must retain the whole configured video frame cache group
|
||||
// duration (the key frame must survive until GetVideoFrameCache reads it), which is more than the normal
|
||||
// ~1s retransmit window. In that case the target / cap are computed below from pps; otherwise
|
||||
// keep the original fast path.
|
||||
|
||||
@@ -77,7 +77,7 @@ func TestVideoFrameCacheReadsFromBucket(t *testing.T) {
|
||||
b.markVideoFrameCacheLocked(videoFrameCacheMarkPkt(103, 4000, false))
|
||||
b.markVideoFrameCacheLocked(videoFrameCacheMarkPkt(104, 5000, false))
|
||||
|
||||
// GOP is [key frame .. head] = 101..104
|
||||
// video frame cache group is [key frame .. head] = 101..104
|
||||
pkts, ok := b.GetVideoFrameCache()
|
||||
require.True(t, ok)
|
||||
require.Len(t, pkts, 4)
|
||||
@@ -179,7 +179,7 @@ func TestVideoFrameCacheSpanUsesMaxTimestamp(t *testing.T) {
|
||||
b.markVideoFrameCacheLocked(videoFrameCacheMarkPkt(102, 1100, false))
|
||||
require.Equal(t, uint64(1200), b.videoFrameCacheLatestTS)
|
||||
|
||||
// a new key frame resets the span to itself (a stale packet cannot stretch the new GOP)
|
||||
// a new key frame resets the span to itself (a stale packet cannot stretch the new video frame cache group)
|
||||
b.markVideoFrameCacheLocked(videoFrameCacheMarkPkt(103, 5000, true))
|
||||
require.Equal(t, uint64(5000), b.videoFrameCacheLatestTS)
|
||||
b.markVideoFrameCacheLocked(videoFrameCacheMarkPkt(104, 4000, false)) // stale, older than the new key frame
|
||||
|
||||
+2
-2
@@ -93,12 +93,12 @@ func WithForwardStats(forwardStats *ForwardStats) ReceiverOpts {
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultVideoFrameCacheMaxDuration bounds the cached GOP (and sizes the retransmit bucket) when the video
|
||||
// DefaultVideoFrameCacheMaxDuration bounds the cached video frame cache group (and sizes the retransmit bucket) when the video
|
||||
// frame cache is enabled via WithVideoFrameCache.
|
||||
const DefaultVideoFrameCacheMaxDuration = 1 * time.Second
|
||||
|
||||
// WithVideoFrameCache enables the video frame cache on the receiver so a newly added down track is
|
||||
// bootstrapped by replaying the cached GOP instead of triggering a PLI. maxDuration bounds the
|
||||
// bootstrapped by replaying the cached video frame cache group instead of triggering a PLI. maxDuration bounds the
|
||||
// cached video frame (<= 0 uses DefaultVideoFrameCacheMaxDuration). No-op for audio receivers.
|
||||
func WithVideoFrameCache(maxDuration time.Duration) ReceiverOpts {
|
||||
return func(w *WebRTCReceiver) *WebRTCReceiver {
|
||||
|
||||
@@ -623,15 +623,15 @@ func (r *ReceiverBase) EnableVideoFrameCache(maxDuration time.Duration) {
|
||||
// pathological case where live keeps outrunning the replay).
|
||||
const videoFrameCacheReplayMaxCatchupRounds = 8
|
||||
|
||||
// replayVideoFrameCache bootstraps a freshly added down track by replaying the publisher's cached GOP, then the
|
||||
// replayVideoFrameCache bootstraps a freshly added down track by replaying the publisher's cached video frame cache group, then the
|
||||
// packets accumulated since, until it catches up to the live forwarding point. Packets are paced at
|
||||
// half the (estimated) video frame interval so the replay runs at ~2x real time and converges on
|
||||
// live. It writes directly to the track (which is not yet in the live broadcast), so there is no
|
||||
// interleaving with live packets.
|
||||
//
|
||||
// It replays the highest spatial layer that currently has a cached GOP: a subscriber requesting full
|
||||
// It replays the highest spatial layer that currently has a cached video frame cache group: a subscriber requesting full
|
||||
// quality has its forwarder targeting the top layer, and lower layers may not even be flowing (e.g.
|
||||
// paused by dynacast), so layer 0 often has no GOP.
|
||||
// paused by dynacast), so layer 0 often has no video frame cache group.
|
||||
func (r *ReceiverBase) replayVideoFrameCache(track TrackSender) {
|
||||
var (
|
||||
buff buffer.BufferProvider
|
||||
@@ -650,7 +650,7 @@ func (r *ReceiverBase) replayVideoFrameCache(track TrackSender) {
|
||||
}
|
||||
|
||||
if layer == buffer.InvalidLayerSpatial {
|
||||
// no usable GOP cached on any layer - the down track falls back to requesting a key frame (PLI)
|
||||
// no usable video frame cache group cached on any layer - the down track falls back to requesting a key frame (PLI)
|
||||
missCount := r.videoFrameCacheMissCount.Inc()
|
||||
r.params.Logger.Debugw(
|
||||
"subscriber bootstrap: video frame cache miss, falling back to PLI",
|
||||
|
||||
Reference in New Issue
Block a user