Squashed 'livekit-server/' content from commit 154b4d26
git-subtree-dir: livekit-server git-subtree-split: 154b4d26b769c68a03c096124094b97bf61a996f
This commit is contained in:
@@ -0,0 +1,971 @@
|
||||
// Copyright 2023 LiveKit, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package rtpstats
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap/zapcore"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/livekit/mediatransportutil"
|
||||
"github.com/livekit/protocol/livekit"
|
||||
"github.com/livekit/protocol/utils"
|
||||
"github.com/livekit/protocol/utils/mono"
|
||||
)
|
||||
|
||||
const (
|
||||
cFirstPacketTimeAdjustWindow = 2 * time.Minute
|
||||
cFirstPacketTimeAdjustThreshold = 15 * 1e9
|
||||
|
||||
cSequenceNumberLargeJumpThreshold = 100
|
||||
)
|
||||
|
||||
// -------------------------------------------------------
|
||||
|
||||
type RTPDeltaInfo struct {
|
||||
StartTime time.Time
|
||||
EndTime time.Time
|
||||
Packets uint32
|
||||
Bytes uint64
|
||||
HeaderBytes uint64
|
||||
PacketsDuplicate uint32
|
||||
BytesDuplicate uint64
|
||||
HeaderBytesDuplicate uint64
|
||||
PacketsPadding uint32
|
||||
BytesPadding uint64
|
||||
HeaderBytesPadding uint64
|
||||
PacketsLost uint32
|
||||
PacketsMissing uint32
|
||||
PacketsOutOfOrder uint32
|
||||
Frames uint32
|
||||
RttMax uint32
|
||||
JitterMax float64
|
||||
Nacks uint32
|
||||
NackRepeated uint32
|
||||
Plis uint32
|
||||
Firs uint32
|
||||
}
|
||||
|
||||
func (r *RTPDeltaInfo) MarshalLogObject(e zapcore.ObjectEncoder) error {
|
||||
if r == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
e.AddTime("StartTime", r.StartTime)
|
||||
e.AddTime("EndTime", r.EndTime)
|
||||
e.AddUint32("Packets", r.Packets)
|
||||
e.AddUint64("Bytes", r.Bytes)
|
||||
e.AddUint64("HeaderBytes", r.HeaderBytes)
|
||||
e.AddUint32("PacketsDuplicate", r.PacketsDuplicate)
|
||||
e.AddUint64("BytesDuplicate", r.BytesDuplicate)
|
||||
e.AddUint64("HeaderBytesDuplicate", r.HeaderBytesDuplicate)
|
||||
e.AddUint32("PacketsPadding", r.PacketsPadding)
|
||||
e.AddUint64("BytesPadding", r.BytesPadding)
|
||||
e.AddUint64("HeaderBytesPadding", r.HeaderBytesPadding)
|
||||
e.AddUint32("PacketsLost", r.PacketsLost)
|
||||
e.AddUint32("PacketsMissing", r.PacketsMissing)
|
||||
e.AddUint32("PacketsOutOfOrder", r.PacketsOutOfOrder)
|
||||
e.AddUint32("Frames", r.Frames)
|
||||
e.AddUint32("RttMax", r.RttMax)
|
||||
e.AddFloat64("JitterMax", r.JitterMax)
|
||||
e.AddUint32("Nacks", r.Nacks)
|
||||
e.AddUint32("NackRepeated", r.NackRepeated)
|
||||
e.AddUint32("Plis", r.Plis)
|
||||
e.AddUint32("Firs", r.Firs)
|
||||
return nil
|
||||
}
|
||||
|
||||
// -------------------------------------------------------
|
||||
|
||||
type snapshot struct {
|
||||
snapshotLite
|
||||
|
||||
headerBytes uint64
|
||||
|
||||
packetsDuplicate uint64
|
||||
bytesDuplicate uint64
|
||||
headerBytesDuplicate uint64
|
||||
|
||||
packetsPadding uint64
|
||||
bytesPadding uint64
|
||||
headerBytesPadding uint64
|
||||
|
||||
frames uint32
|
||||
|
||||
plis uint32
|
||||
firs uint32
|
||||
|
||||
maxRtt uint32
|
||||
maxJitter float64
|
||||
}
|
||||
|
||||
func (s *snapshot) MarshalLogObject(e zapcore.ObjectEncoder) error {
|
||||
if s == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
e.AddObject("snapshotLite", &s.snapshotLite)
|
||||
e.AddUint64("headerBytes", s.headerBytes)
|
||||
e.AddUint64("packetsDuplicate", s.packetsDuplicate)
|
||||
e.AddUint64("bytesDuplicate", s.bytesDuplicate)
|
||||
e.AddUint64("headerBytesDuplicate", s.headerBytesDuplicate)
|
||||
e.AddUint64("packetsPadding", s.packetsPadding)
|
||||
e.AddUint64("bytesPadding", s.bytesPadding)
|
||||
e.AddUint64("headerBytesPadding", s.headerBytesPadding)
|
||||
e.AddUint32("frames", s.frames)
|
||||
e.AddUint32("plis", s.plis)
|
||||
e.AddUint32("firs", s.firs)
|
||||
e.AddUint32("maxRtt", s.maxRtt)
|
||||
e.AddFloat64("maxJitter", s.maxJitter)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *snapshot) maybeUpdateMaxRTT(rtt uint32) {
|
||||
if rtt > s.maxRtt {
|
||||
s.maxRtt = rtt
|
||||
}
|
||||
}
|
||||
|
||||
func (s *snapshot) maybeUpdateMaxJitter(jitter float64) {
|
||||
if jitter > s.maxJitter {
|
||||
s.maxJitter = jitter
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
type wrappedRTPDriftLogger struct {
|
||||
*livekit.RTPDrift
|
||||
}
|
||||
|
||||
func (w wrappedRTPDriftLogger) MarshalLogObject(e zapcore.ObjectEncoder) error {
|
||||
rd := w.RTPDrift
|
||||
if rd == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
e.AddTime("StartTime", rd.StartTime.AsTime())
|
||||
e.AddTime("EndTime", rd.EndTime.AsTime())
|
||||
e.AddFloat64("Duration", rd.Duration)
|
||||
e.AddUint64("StartTimestamp", rd.StartTimestamp)
|
||||
e.AddUint64("EndTimestamp", rd.EndTimestamp)
|
||||
e.AddUint64("RtpClockTicks", rd.RtpClockTicks)
|
||||
e.AddInt64("DriftSamples", rd.DriftSamples)
|
||||
e.AddFloat64("DriftMs", rd.DriftMs)
|
||||
e.AddFloat64("ClockRate", rd.ClockRate)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
type WrappedRTCPSenderReportStateLogger struct {
|
||||
*livekit.RTCPSenderReportState
|
||||
}
|
||||
|
||||
func (w WrappedRTCPSenderReportStateLogger) MarshalLogObject(e zapcore.ObjectEncoder) error {
|
||||
rsrs := w.RTCPSenderReportState
|
||||
if rsrs == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
e.AddUint32("RtpTimestamp", rsrs.RtpTimestamp)
|
||||
e.AddUint64("RtpTimestampExt", rsrs.RtpTimestampExt)
|
||||
e.AddTime("NtpTimestamp", mediatransportutil.NtpTime(rsrs.NtpTimestamp).Time())
|
||||
e.AddTime("At", time.Unix(0, rsrs.At))
|
||||
e.AddTime("AtAdjusted", time.Unix(0, rsrs.AtAdjusted))
|
||||
e.AddUint32("Packets", rsrs.Packets)
|
||||
e.AddUint64("Octets", rsrs.Octets)
|
||||
return nil
|
||||
}
|
||||
|
||||
func RTCPSenderReportPropagationDelay(rsrs *livekit.RTCPSenderReportState, passThrough bool) time.Duration {
|
||||
if passThrough {
|
||||
return 0
|
||||
}
|
||||
|
||||
return time.Unix(0, rsrs.AtAdjusted).Sub(mediatransportutil.NtpTime(rsrs.NtpTimestamp).Time())
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
type rtpStatsBase struct {
|
||||
*rtpStatsBaseLite
|
||||
|
||||
firstTime int64
|
||||
firstTimeAdjustment time.Duration
|
||||
highestTime int64
|
||||
|
||||
lastTransit uint64
|
||||
lastJitterExtTimestamp uint64
|
||||
|
||||
headerBytes uint64
|
||||
|
||||
packetsDuplicate uint64
|
||||
bytesDuplicate uint64
|
||||
headerBytesDuplicate uint64
|
||||
|
||||
packetsPadding uint64
|
||||
bytesPadding uint64
|
||||
headerBytesPadding uint64
|
||||
|
||||
frames uint32
|
||||
|
||||
jitter float64
|
||||
maxJitter float64
|
||||
|
||||
firs uint32
|
||||
lastFir time.Time
|
||||
|
||||
keyFrames uint32
|
||||
lastKeyFrame time.Time
|
||||
|
||||
rtt uint32
|
||||
maxRtt uint32
|
||||
|
||||
srFirst *livekit.RTCPSenderReportState
|
||||
srNewest *livekit.RTCPSenderReportState
|
||||
|
||||
nextSnapshotID uint32
|
||||
snapshots []snapshot
|
||||
}
|
||||
|
||||
func newRTPStatsBase(params RTPStatsParams) *rtpStatsBase {
|
||||
return &rtpStatsBase{
|
||||
rtpStatsBaseLite: newRTPStatsBaseLite(params),
|
||||
nextSnapshotID: cFirstSnapshotID,
|
||||
snapshots: make([]snapshot, 2),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *rtpStatsBase) seed(from *rtpStatsBase) bool {
|
||||
if !r.rtpStatsBaseLite.seed(from.rtpStatsBaseLite) {
|
||||
return false
|
||||
}
|
||||
|
||||
r.firstTime = from.firstTime
|
||||
r.firstTimeAdjustment = from.firstTimeAdjustment
|
||||
r.highestTime = from.highestTime
|
||||
|
||||
r.lastTransit = from.lastTransit
|
||||
r.lastJitterExtTimestamp = from.lastJitterExtTimestamp
|
||||
|
||||
r.headerBytes = from.headerBytes
|
||||
|
||||
r.packetsDuplicate = from.packetsDuplicate
|
||||
r.bytesDuplicate = from.bytesDuplicate
|
||||
r.headerBytesDuplicate = from.headerBytesDuplicate
|
||||
|
||||
r.packetsPadding = from.packetsPadding
|
||||
r.bytesPadding = from.bytesPadding
|
||||
r.headerBytesPadding = from.headerBytesPadding
|
||||
|
||||
r.frames = from.frames
|
||||
|
||||
r.jitter = from.jitter
|
||||
r.maxJitter = from.maxJitter
|
||||
|
||||
r.firs = from.firs
|
||||
r.lastFir = from.lastFir
|
||||
|
||||
r.keyFrames = from.keyFrames
|
||||
r.lastKeyFrame = from.lastKeyFrame
|
||||
|
||||
r.rtt = from.rtt
|
||||
r.maxRtt = from.maxRtt
|
||||
|
||||
r.srFirst = utils.CloneProto(from.srFirst)
|
||||
r.srNewest = utils.CloneProto(from.srNewest)
|
||||
|
||||
r.nextSnapshotID = from.nextSnapshotID
|
||||
r.snapshots = make([]snapshot, cap(from.snapshots))
|
||||
copy(r.snapshots, from.snapshots)
|
||||
return true
|
||||
}
|
||||
|
||||
func (r *rtpStatsBase) newSnapshotID(extStartSN uint64) uint32 {
|
||||
id := r.nextSnapshotID
|
||||
r.nextSnapshotID++
|
||||
|
||||
if cap(r.snapshots) < int(r.nextSnapshotID-cFirstSnapshotID) {
|
||||
snapshots := make([]snapshot, r.nextSnapshotID-cFirstSnapshotID)
|
||||
copy(snapshots, r.snapshots)
|
||||
r.snapshots = snapshots
|
||||
}
|
||||
|
||||
if r.initialized {
|
||||
r.snapshots[id-cFirstSnapshotID] = initSnapshot(mono.UnixNano(), extStartSN)
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
func (r *rtpStatsBase) UpdateFir(firCount uint32) {
|
||||
r.lock.Lock()
|
||||
defer r.lock.Unlock()
|
||||
|
||||
if r.endTime != 0 {
|
||||
return
|
||||
}
|
||||
|
||||
r.firs += firCount
|
||||
}
|
||||
|
||||
func (r *rtpStatsBase) UpdateFirTime() {
|
||||
r.lock.Lock()
|
||||
defer r.lock.Unlock()
|
||||
|
||||
if r.endTime != 0 {
|
||||
return
|
||||
}
|
||||
|
||||
r.lastFir = time.Now()
|
||||
}
|
||||
|
||||
func (r *rtpStatsBase) UpdateKeyFrame(kfCount uint32) {
|
||||
r.lock.Lock()
|
||||
defer r.lock.Unlock()
|
||||
|
||||
if r.endTime != 0 {
|
||||
return
|
||||
}
|
||||
|
||||
r.keyFrames += kfCount
|
||||
r.lastKeyFrame = time.Now()
|
||||
}
|
||||
|
||||
func (r *rtpStatsBase) UpdateRtt(rtt uint32) {
|
||||
r.lock.Lock()
|
||||
defer r.lock.Unlock()
|
||||
|
||||
if r.endTime != 0 {
|
||||
return
|
||||
}
|
||||
|
||||
r.rtt = rtt
|
||||
if rtt > r.maxRtt {
|
||||
r.maxRtt = rtt
|
||||
}
|
||||
|
||||
for i := uint32(0); i < r.nextSnapshotID-cFirstSnapshotID; i++ {
|
||||
s := &r.snapshots[i]
|
||||
if rtt > s.maxRtt {
|
||||
s.maxRtt = rtt
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *rtpStatsBase) GetRtt() uint32 {
|
||||
r.lock.RLock()
|
||||
defer r.lock.RUnlock()
|
||||
|
||||
return r.rtt
|
||||
}
|
||||
|
||||
func (r *rtpStatsBase) maybeAdjustFirstPacketTime(srData *livekit.RTCPSenderReportState, tsOffset uint64, extStartTS uint64) (err error, loggingFields []interface{}) {
|
||||
nowNano := mono.UnixNano()
|
||||
if time.Duration(nowNano-r.startTime) > cFirstPacketTimeAdjustWindow {
|
||||
return
|
||||
}
|
||||
|
||||
// for some time after the start, adjust time of first packet.
|
||||
// Helps improve accuracy of expected timestamp calculation.
|
||||
// Adjusting only one way, i. e. if the first sample experienced
|
||||
// abnormal delay (maybe due to pacing or maybe due to queuing
|
||||
// in some network element along the way), push back first time
|
||||
// to an earlier instance.
|
||||
timeSinceReceive := time.Duration(nowNano - srData.AtAdjusted)
|
||||
extNowTS := srData.RtpTimestampExt - tsOffset + uint64(timeSinceReceive.Nanoseconds()*int64(r.params.ClockRate)/1e9)
|
||||
samplesDiff := int64(extNowTS - extStartTS)
|
||||
if samplesDiff < 0 {
|
||||
// out-of-order, skip
|
||||
return
|
||||
}
|
||||
|
||||
samplesDuration := time.Duration(float64(samplesDiff) / float64(r.params.ClockRate) * float64(time.Second))
|
||||
timeSinceFirst := time.Duration(nowNano - r.firstTime)
|
||||
now := r.firstTime + timeSinceFirst.Nanoseconds()
|
||||
firstTime := now - samplesDuration.Nanoseconds()
|
||||
|
||||
getFields := func() []interface{} {
|
||||
return []interface{}{
|
||||
"startTime", time.Unix(0, r.startTime),
|
||||
"nowTime", time.Unix(0, now),
|
||||
"before", time.Unix(0, r.firstTime),
|
||||
"after", time.Unix(0, firstTime),
|
||||
"adjustment", time.Duration(r.firstTime - firstTime),
|
||||
"extNowTS", extNowTS,
|
||||
"extStartTS", extStartTS,
|
||||
"srData", WrappedRTCPSenderReportStateLogger{srData},
|
||||
"tsOffset", tsOffset,
|
||||
"timeSinceReceive", timeSinceReceive,
|
||||
"timeSinceFirst", timeSinceFirst,
|
||||
"samplesDiff", samplesDiff,
|
||||
"samplesDuration", samplesDuration,
|
||||
}
|
||||
}
|
||||
|
||||
if firstTime < r.firstTime {
|
||||
if r.firstTime-firstTime > cFirstPacketTimeAdjustThreshold {
|
||||
err = errors.New("adjusting first packet time, too big, ignoring")
|
||||
loggingFields = getFields()
|
||||
} else {
|
||||
r.logger.Debugw("adjusting first packet time", getFields()...)
|
||||
r.firstTimeAdjustment += time.Duration(r.firstTime - firstTime)
|
||||
r.firstTime = firstTime
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (r *rtpStatsBase) getPacketsSeenMinusPadding(extStartSN, extHighestSN uint64) uint64 {
|
||||
packetsSeen := r.getPacketsSeen(extStartSN, extHighestSN)
|
||||
if r.packetsPadding > packetsSeen {
|
||||
return 0
|
||||
}
|
||||
|
||||
return packetsSeen - r.packetsPadding
|
||||
}
|
||||
|
||||
func (r *rtpStatsBase) getPacketsSeenPlusDuplicates(extStartSN, extHighestSN uint64) uint64 {
|
||||
return r.getPacketsSeen(extStartSN, extHighestSN) + r.packetsDuplicate
|
||||
}
|
||||
|
||||
func (r *rtpStatsBase) deltaInfo(
|
||||
snapshotID uint32,
|
||||
extStartSN uint64,
|
||||
extHighestSN uint64,
|
||||
) (deltaInfo *RTPDeltaInfo, err error, loggingFields []interface{}) {
|
||||
then, now := r.getAndResetSnapshot(snapshotID, extStartSN, extHighestSN)
|
||||
if now == nil || then == nil {
|
||||
return
|
||||
}
|
||||
|
||||
startTime := then.startTime
|
||||
endTime := now.startTime
|
||||
|
||||
packetsExpected := now.extStartSN - then.extStartSN
|
||||
if then.extStartSN > extHighestSN {
|
||||
packetsExpected = 0
|
||||
}
|
||||
if packetsExpected > cNumSequenceNumbers {
|
||||
loggingFields = []interface{}{
|
||||
"snapshotID", snapshotID,
|
||||
"snapshotNow", now,
|
||||
"snapshotThen", then,
|
||||
"duration", time.Duration(endTime - startTime),
|
||||
"packetsExpected", packetsExpected,
|
||||
}
|
||||
err = errors.New("too many packets expected in delta")
|
||||
return
|
||||
}
|
||||
if packetsExpected == 0 {
|
||||
deltaInfo = &RTPDeltaInfo{
|
||||
StartTime: time.Unix(0, startTime),
|
||||
EndTime: time.Unix(0, endTime),
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
packetsLost := uint32(now.packetsLost - then.packetsLost)
|
||||
if int32(packetsLost) < 0 {
|
||||
packetsLost = 0
|
||||
}
|
||||
|
||||
// padding packets delta could be higher than expected due to out-of-order padding packets
|
||||
packetsPadding := now.packetsPadding - then.packetsPadding
|
||||
if packetsExpected < packetsPadding {
|
||||
loggingFields = []interface{}{
|
||||
"snapshotID", snapshotID,
|
||||
"snapshotNow", now,
|
||||
"snapshotThen", then,
|
||||
"duration", time.Duration(endTime - startTime),
|
||||
"packetsExpected", packetsExpected,
|
||||
"packetsPadding", packetsPadding,
|
||||
"packetsLost", packetsLost,
|
||||
}
|
||||
err = errors.New("padding packets more than expected")
|
||||
packetsExpected = 0
|
||||
} else {
|
||||
packetsExpected -= packetsPadding
|
||||
}
|
||||
|
||||
deltaInfo = &RTPDeltaInfo{
|
||||
StartTime: time.Unix(0, startTime),
|
||||
EndTime: time.Unix(0, endTime),
|
||||
Packets: uint32(packetsExpected),
|
||||
Bytes: now.bytes - then.bytes,
|
||||
HeaderBytes: now.headerBytes - then.headerBytes,
|
||||
PacketsDuplicate: uint32(now.packetsDuplicate - then.packetsDuplicate),
|
||||
BytesDuplicate: now.bytesDuplicate - then.bytesDuplicate,
|
||||
HeaderBytesDuplicate: now.headerBytesDuplicate - then.headerBytesDuplicate,
|
||||
PacketsPadding: uint32(packetsPadding),
|
||||
BytesPadding: now.bytesPadding - then.bytesPadding,
|
||||
HeaderBytesPadding: now.headerBytesPadding - then.headerBytesPadding,
|
||||
PacketsLost: packetsLost,
|
||||
PacketsOutOfOrder: uint32(now.packetsOutOfOrder - then.packetsOutOfOrder),
|
||||
Frames: now.frames - then.frames,
|
||||
RttMax: then.maxRtt,
|
||||
JitterMax: then.maxJitter / float64(r.params.ClockRate) * 1e6,
|
||||
Nacks: now.nacks - then.nacks,
|
||||
Plis: now.plis - then.plis,
|
||||
Firs: now.firs - then.firs,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (r *rtpStatsBase) marshalLogObject(
|
||||
e zapcore.ObjectEncoder,
|
||||
packetsExpected, packetsSeenMinusPadding uint64,
|
||||
extStartTS, extHighestTS uint64,
|
||||
) (float64, error) {
|
||||
if r == nil {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
elapsedSeconds, err := r.rtpStatsBaseLite.marshalLogObject(e, packetsExpected, packetsSeenMinusPadding)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
e.AddTime("firstTime", time.Unix(0, r.firstTime))
|
||||
e.AddDuration("firstTimeAdjustment", r.firstTimeAdjustment)
|
||||
e.AddTime("highestTime", time.Unix(0, r.highestTime))
|
||||
|
||||
e.AddUint64("headerBytes", r.headerBytes)
|
||||
|
||||
e.AddUint64("packetsDuplicate", r.packetsDuplicate)
|
||||
e.AddFloat64("packetsDuplicateRate", float64(r.packetsDuplicate)/elapsedSeconds)
|
||||
e.AddUint64("bytesDuplicate", r.bytesDuplicate)
|
||||
e.AddFloat64("bitrateDuplicate", float64(r.bytesDuplicate)*8.0/elapsedSeconds)
|
||||
e.AddUint64("headerBytesDuplicate", r.headerBytesDuplicate)
|
||||
|
||||
e.AddUint64("packetsPadding", r.packetsPadding)
|
||||
e.AddFloat64("packetsPaddingRate", float64(r.packetsPadding)/elapsedSeconds)
|
||||
e.AddUint64("bytesPadding", r.bytesPadding)
|
||||
e.AddFloat64("bitratePadding", float64(r.bytesPadding)*8.0/elapsedSeconds)
|
||||
e.AddUint64("headerBytesPadding", r.headerBytesPadding)
|
||||
|
||||
e.AddUint32("frames", r.frames)
|
||||
e.AddFloat64("frameRate", float64(r.frames)/elapsedSeconds)
|
||||
|
||||
e.AddFloat64("jitter", r.jitter)
|
||||
e.AddFloat64("maxJitter", r.maxJitter)
|
||||
|
||||
e.AddUint32("firs", r.firs)
|
||||
e.AddTime("lastFir", r.lastFir)
|
||||
|
||||
e.AddUint32("keyFrames", r.keyFrames)
|
||||
e.AddTime("lastKeyFrame", r.lastKeyFrame)
|
||||
|
||||
e.AddUint32("rtt", r.rtt)
|
||||
e.AddUint32("maxRtt", r.maxRtt)
|
||||
|
||||
e.AddObject("srFirst", WrappedRTCPSenderReportStateLogger{r.srFirst})
|
||||
e.AddObject("srNewest", WrappedRTCPSenderReportStateLogger{r.srNewest})
|
||||
|
||||
packetDrift, ntpReportDrift, receivedReportDrift, rebasedReportDrift := r.getDrift(extStartTS, extHighestTS)
|
||||
e.AddObject("packetDrift", wrappedRTPDriftLogger{packetDrift})
|
||||
e.AddObject("ntpReportDrift", wrappedRTPDriftLogger{ntpReportDrift})
|
||||
e.AddObject("receivedReportDrift", wrappedRTPDriftLogger{receivedReportDrift})
|
||||
e.AddObject("rebasedReportDrift", wrappedRTPDriftLogger{rebasedReportDrift})
|
||||
return elapsedSeconds, nil
|
||||
}
|
||||
|
||||
func (r *rtpStatsBase) toProto(
|
||||
packetsExpected, packetsSeenMinusPadding, packetsLost uint64,
|
||||
extStartTS, extHighestTS uint64,
|
||||
jitter, maxJitter float64,
|
||||
) *livekit.RTPStats {
|
||||
p := r.rtpStatsBaseLite.toProto(packetsExpected, packetsSeenMinusPadding, packetsLost)
|
||||
if p == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
p.HeaderBytes = r.headerBytes
|
||||
|
||||
p.PacketsDuplicate = uint32(r.packetsDuplicate)
|
||||
p.PacketDuplicateRate = float64(r.packetsDuplicate) / p.Duration
|
||||
p.BytesDuplicate = r.bytesDuplicate
|
||||
p.BitrateDuplicate = float64(r.bytesDuplicate) * 8.0 / p.Duration
|
||||
p.HeaderBytesDuplicate = r.headerBytesDuplicate
|
||||
|
||||
p.PacketsPadding = uint32(r.packetsPadding)
|
||||
p.PacketPaddingRate = float64(r.packetsPadding) / p.Duration
|
||||
p.BytesPadding = r.bytesPadding
|
||||
p.BitratePadding = float64(r.bytesPadding) * 8.0 / p.Duration
|
||||
p.HeaderBytesPadding = r.headerBytesPadding
|
||||
|
||||
p.Frames = r.frames
|
||||
p.FrameRate = float64(r.frames) / p.Duration
|
||||
|
||||
p.KeyFrames = r.keyFrames
|
||||
p.LastKeyFrame = timestamppb.New(r.lastKeyFrame)
|
||||
|
||||
p.JitterCurrent = jitter / float64(r.params.ClockRate) * 1e6
|
||||
p.JitterMax = maxJitter / float64(r.params.ClockRate) * 1e6
|
||||
|
||||
p.Firs = r.firs
|
||||
p.LastFir = timestamppb.New(r.lastFir)
|
||||
|
||||
p.RttCurrent = r.rtt
|
||||
p.RttMax = r.maxRtt
|
||||
|
||||
p.PacketDrift, p.NtpReportDrift, p.ReceivedReportDrift, p.RebasedReportDrift = r.getDrift(extStartTS, extHighestTS)
|
||||
return p
|
||||
}
|
||||
|
||||
func (r *rtpStatsBase) updateJitter(ets uint64, packetTime int64) float64 {
|
||||
// Do not update jitter on multiple packets of same frame.
|
||||
// All packets of a frame have the same time stamp.
|
||||
// NOTE: This does not protect against using more than one packet of the same frame
|
||||
// if packets arrive out-of-order. For example,
|
||||
// p1f1 -> p1f2 -> p2f1
|
||||
// In this case, p2f1 (packet 2, frame 1) will still be used in jitter calculation
|
||||
// although it is the second packet of a frame because of out-of-order receival.
|
||||
if r.lastJitterExtTimestamp != ets {
|
||||
timeSinceFirst := packetTime - r.firstTime
|
||||
packetTimeRTP := uint64(timeSinceFirst * int64(r.params.ClockRate) / 1e9)
|
||||
transit := packetTimeRTP - ets
|
||||
|
||||
if r.lastTransit != 0 {
|
||||
d := int64(transit - r.lastTransit)
|
||||
if d < 0 {
|
||||
d = -d
|
||||
}
|
||||
r.jitter += (float64(d) - r.jitter) / 16
|
||||
if r.jitter > r.maxJitter {
|
||||
r.maxJitter = r.jitter
|
||||
}
|
||||
|
||||
for i := uint32(0); i < r.nextSnapshotID-cFirstSnapshotID; i++ {
|
||||
r.snapshots[i].maybeUpdateMaxJitter(r.jitter)
|
||||
}
|
||||
}
|
||||
|
||||
r.lastTransit = transit
|
||||
r.lastJitterExtTimestamp = ets
|
||||
}
|
||||
return r.jitter
|
||||
}
|
||||
|
||||
func (r *rtpStatsBase) getAndResetSnapshot(snapshotID uint32, extStartSN uint64, extHighestSN uint64) (*snapshot, *snapshot) {
|
||||
if !r.initialized {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
idx := snapshotID - cFirstSnapshotID
|
||||
then := r.snapshots[idx]
|
||||
if !then.isValid {
|
||||
then = initSnapshot(r.startTime, extStartSN)
|
||||
r.snapshots[idx] = then
|
||||
}
|
||||
|
||||
// snapshot now
|
||||
now := r.getSnapshot(mono.UnixNano(), extHighestSN+1)
|
||||
r.snapshots[idx] = now
|
||||
return &then, &now
|
||||
}
|
||||
|
||||
func (r *rtpStatsBase) getDrift(extStartTS, extHighestTS uint64) (
|
||||
packetDrift *livekit.RTPDrift,
|
||||
ntpReportDrift *livekit.RTPDrift,
|
||||
receivedReportDrift *livekit.RTPDrift,
|
||||
rebasedReportDrift *livekit.RTPDrift,
|
||||
) {
|
||||
if r.firstTime != 0 {
|
||||
elapsed := r.highestTime - r.firstTime
|
||||
rtpClockTicks := extHighestTS - extStartTS
|
||||
driftSamples := int64(rtpClockTicks - uint64(elapsed*int64(r.params.ClockRate)/1e9))
|
||||
if elapsed > 0 {
|
||||
elapsedSeconds := time.Duration(elapsed).Seconds()
|
||||
packetDrift = &livekit.RTPDrift{
|
||||
StartTime: timestamppb.New(time.Unix(0, r.firstTime)),
|
||||
EndTime: timestamppb.New(time.Unix(0, r.highestTime)),
|
||||
Duration: elapsedSeconds,
|
||||
StartTimestamp: extStartTS,
|
||||
EndTimestamp: extHighestTS,
|
||||
RtpClockTicks: rtpClockTicks,
|
||||
DriftSamples: driftSamples,
|
||||
DriftMs: (float64(driftSamples) * 1000) / float64(r.params.ClockRate),
|
||||
ClockRate: float64(rtpClockTicks) / elapsedSeconds,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if r.srFirst != nil && r.srNewest != nil && r.srFirst.RtpTimestamp != r.srNewest.RtpTimestamp {
|
||||
rtpClockTicks := r.srNewest.RtpTimestampExt - r.srFirst.RtpTimestampExt
|
||||
|
||||
elapsed := mediatransportutil.NtpTime(r.srNewest.NtpTimestamp).Time().Sub(mediatransportutil.NtpTime(r.srFirst.NtpTimestamp).Time())
|
||||
if elapsed.Seconds() > 0.0 {
|
||||
driftSamples := int64(rtpClockTicks - uint64(elapsed.Nanoseconds()*int64(r.params.ClockRate)/1e9))
|
||||
ntpReportDrift = &livekit.RTPDrift{
|
||||
StartTime: timestamppb.New(mediatransportutil.NtpTime(r.srFirst.NtpTimestamp).Time()),
|
||||
EndTime: timestamppb.New(mediatransportutil.NtpTime(r.srNewest.NtpTimestamp).Time()),
|
||||
Duration: elapsed.Seconds(),
|
||||
StartTimestamp: r.srFirst.RtpTimestampExt,
|
||||
EndTimestamp: r.srNewest.RtpTimestampExt,
|
||||
RtpClockTicks: rtpClockTicks,
|
||||
DriftSamples: driftSamples,
|
||||
DriftMs: (float64(driftSamples) * 1000) / float64(r.params.ClockRate),
|
||||
ClockRate: float64(rtpClockTicks) / elapsed.Seconds(),
|
||||
}
|
||||
}
|
||||
|
||||
elapsed = time.Duration(r.srNewest.At - r.srFirst.At)
|
||||
if elapsed.Seconds() > 0.0 {
|
||||
driftSamples := int64(rtpClockTicks - uint64(elapsed.Nanoseconds()*int64(r.params.ClockRate)/1e9))
|
||||
receivedReportDrift = &livekit.RTPDrift{
|
||||
StartTime: timestamppb.New(time.Unix(0, r.srFirst.At)),
|
||||
EndTime: timestamppb.New(time.Unix(0, r.srNewest.At)),
|
||||
Duration: elapsed.Seconds(),
|
||||
StartTimestamp: r.srFirst.RtpTimestampExt,
|
||||
EndTimestamp: r.srNewest.RtpTimestampExt,
|
||||
RtpClockTicks: rtpClockTicks,
|
||||
DriftSamples: driftSamples,
|
||||
DriftMs: (float64(driftSamples) * 1000) / float64(r.params.ClockRate),
|
||||
ClockRate: float64(rtpClockTicks) / elapsed.Seconds(),
|
||||
}
|
||||
}
|
||||
|
||||
elapsed = time.Duration(r.srNewest.AtAdjusted - r.srFirst.AtAdjusted)
|
||||
if elapsed.Seconds() > 0.0 {
|
||||
driftSamples := int64(rtpClockTicks - uint64(elapsed.Nanoseconds()*int64(r.params.ClockRate)/1e9))
|
||||
rebasedReportDrift = &livekit.RTPDrift{
|
||||
StartTime: timestamppb.New(time.Unix(0, r.srFirst.AtAdjusted)),
|
||||
EndTime: timestamppb.New(time.Unix(0, r.srNewest.AtAdjusted)),
|
||||
Duration: elapsed.Seconds(),
|
||||
StartTimestamp: r.srFirst.RtpTimestampExt,
|
||||
EndTimestamp: r.srNewest.RtpTimestampExt,
|
||||
RtpClockTicks: rtpClockTicks,
|
||||
DriftSamples: driftSamples,
|
||||
DriftMs: (float64(driftSamples) * 1000) / float64(r.params.ClockRate),
|
||||
ClockRate: float64(rtpClockTicks) / elapsed.Seconds(),
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (r *rtpStatsBase) updateGapHistogram(gap int) {
|
||||
if gap < 2 {
|
||||
return
|
||||
}
|
||||
|
||||
missing := gap - 1
|
||||
if missing > len(r.gapHistogram) {
|
||||
r.gapHistogram[len(r.gapHistogram)-1]++
|
||||
} else {
|
||||
r.gapHistogram[missing-1]++
|
||||
}
|
||||
}
|
||||
|
||||
func (r *rtpStatsBase) getSnapshot(startTime int64, extStartSN uint64) snapshot {
|
||||
return snapshot{
|
||||
snapshotLite: r.getSnapshotLite(startTime, extStartSN),
|
||||
headerBytes: r.headerBytes,
|
||||
packetsDuplicate: r.packetsDuplicate,
|
||||
bytesDuplicate: r.bytesDuplicate,
|
||||
headerBytesDuplicate: r.headerBytesDuplicate,
|
||||
packetsPadding: r.packetsPadding,
|
||||
bytesPadding: r.bytesPadding,
|
||||
headerBytesPadding: r.headerBytesPadding,
|
||||
frames: r.frames,
|
||||
plis: r.plis,
|
||||
firs: r.firs,
|
||||
maxRtt: r.rtt,
|
||||
maxJitter: r.jitter,
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------
|
||||
|
||||
func initSnapshot(startTime int64, extStartSN uint64) snapshot {
|
||||
return snapshot{
|
||||
snapshotLite: initSnapshotLite(startTime, extStartSN),
|
||||
}
|
||||
}
|
||||
|
||||
func AggregateRTPStats(statsList []*livekit.RTPStats) *livekit.RTPStats {
|
||||
return utils.AggregateRTPStats(statsList, cGapHistogramNumBins)
|
||||
}
|
||||
|
||||
func AggregateRTPDeltaInfo(deltaInfoList []*RTPDeltaInfo) *RTPDeltaInfo {
|
||||
if len(deltaInfoList) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
startTime := int64(0)
|
||||
endTime := int64(0)
|
||||
|
||||
packets := uint32(0)
|
||||
bytes := uint64(0)
|
||||
headerBytes := uint64(0)
|
||||
|
||||
packetsDuplicate := uint32(0)
|
||||
bytesDuplicate := uint64(0)
|
||||
headerBytesDuplicate := uint64(0)
|
||||
|
||||
packetsPadding := uint32(0)
|
||||
bytesPadding := uint64(0)
|
||||
headerBytesPadding := uint64(0)
|
||||
|
||||
packetsLost := uint32(0)
|
||||
packetsMissing := uint32(0)
|
||||
packetsOutOfOrder := uint32(0)
|
||||
|
||||
frames := uint32(0)
|
||||
|
||||
maxRtt := uint32(0)
|
||||
maxJitter := float64(0)
|
||||
|
||||
nacks := uint32(0)
|
||||
plis := uint32(0)
|
||||
firs := uint32(0)
|
||||
|
||||
for _, deltaInfo := range deltaInfoList {
|
||||
if deltaInfo == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if startTime == 0 || startTime > deltaInfo.StartTime.UnixNano() {
|
||||
startTime = deltaInfo.StartTime.UnixNano()
|
||||
}
|
||||
|
||||
if endTime == 0 || endTime < deltaInfo.EndTime.UnixNano() {
|
||||
endTime = deltaInfo.EndTime.UnixNano()
|
||||
}
|
||||
|
||||
packets += deltaInfo.Packets
|
||||
bytes += deltaInfo.Bytes
|
||||
headerBytes += deltaInfo.HeaderBytes
|
||||
|
||||
packetsDuplicate += deltaInfo.PacketsDuplicate
|
||||
bytesDuplicate += deltaInfo.BytesDuplicate
|
||||
headerBytesDuplicate += deltaInfo.HeaderBytesDuplicate
|
||||
|
||||
packetsPadding += deltaInfo.PacketsPadding
|
||||
bytesPadding += deltaInfo.BytesPadding
|
||||
headerBytesPadding += deltaInfo.HeaderBytesPadding
|
||||
|
||||
packetsLost += deltaInfo.PacketsLost
|
||||
packetsMissing += deltaInfo.PacketsMissing
|
||||
packetsOutOfOrder += deltaInfo.PacketsOutOfOrder
|
||||
|
||||
frames += deltaInfo.Frames
|
||||
|
||||
if deltaInfo.RttMax > maxRtt {
|
||||
maxRtt = deltaInfo.RttMax
|
||||
}
|
||||
|
||||
if deltaInfo.JitterMax > maxJitter {
|
||||
maxJitter = deltaInfo.JitterMax
|
||||
}
|
||||
|
||||
nacks += deltaInfo.Nacks
|
||||
plis += deltaInfo.Plis
|
||||
firs += deltaInfo.Firs
|
||||
}
|
||||
if startTime == 0 || endTime == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &RTPDeltaInfo{
|
||||
StartTime: time.Unix(0, startTime),
|
||||
EndTime: time.Unix(0, endTime),
|
||||
Packets: packets,
|
||||
Bytes: bytes,
|
||||
HeaderBytes: headerBytes,
|
||||
PacketsDuplicate: packetsDuplicate,
|
||||
BytesDuplicate: bytesDuplicate,
|
||||
HeaderBytesDuplicate: headerBytesDuplicate,
|
||||
PacketsPadding: packetsPadding,
|
||||
BytesPadding: bytesPadding,
|
||||
HeaderBytesPadding: headerBytesPadding,
|
||||
PacketsLost: packetsLost,
|
||||
PacketsMissing: packetsMissing,
|
||||
PacketsOutOfOrder: packetsOutOfOrder,
|
||||
Frames: frames,
|
||||
RttMax: maxRtt,
|
||||
JitterMax: maxJitter,
|
||||
Nacks: nacks,
|
||||
Plis: plis,
|
||||
Firs: firs,
|
||||
}
|
||||
}
|
||||
|
||||
func ReconcileRTPStatsWithRTX(primaryStats *livekit.RTPStats, rtxStats *livekit.RTPStats) *livekit.RTPStats {
|
||||
if primaryStats == nil || rtxStats == nil {
|
||||
return primaryStats
|
||||
}
|
||||
|
||||
primaryStats.PacketsDuplicate += rtxStats.Packets
|
||||
primaryStats.PacketDuplicateRate = float64(primaryStats.PacketsDuplicate) / primaryStats.Duration
|
||||
|
||||
primaryStats.BytesDuplicate += rtxStats.Bytes
|
||||
primaryStats.HeaderBytesDuplicate += rtxStats.HeaderBytes
|
||||
primaryStats.BitrateDuplicate = float64(primaryStats.BytesDuplicate) * 8.0 / primaryStats.Duration
|
||||
|
||||
primaryStats.PacketsPadding += rtxStats.PacketsPadding
|
||||
primaryStats.PacketPaddingRate = float64(primaryStats.PacketsPadding) / primaryStats.Duration
|
||||
|
||||
primaryStats.BytesPadding += rtxStats.BytesPadding
|
||||
primaryStats.HeaderBytesPadding += rtxStats.HeaderBytesPadding
|
||||
primaryStats.BitratePadding = float64(primaryStats.BytesPadding) * 8.0 / primaryStats.Duration
|
||||
|
||||
// RTX non-padding packets are responses to NACKs, that should discount packets lost,
|
||||
lossAdjustment := rtxStats.Packets - rtxStats.PacketsLost - primaryStats.NackRepeated
|
||||
if int32(lossAdjustment) < 0 {
|
||||
lossAdjustment = 0
|
||||
}
|
||||
if lossAdjustment >= primaryStats.PacketsLost {
|
||||
primaryStats.PacketsLost = 0
|
||||
} else {
|
||||
primaryStats.PacketsLost -= lossAdjustment
|
||||
}
|
||||
primaryStats.PacketLossRate = float64(primaryStats.PacketsLost) / primaryStats.Duration
|
||||
primaryStats.PacketLossPercentage = float32(primaryStats.PacketsLost) / float32(primaryStats.Packets+primaryStats.PacketsPadding+primaryStats.PacketsLost) * 100.0
|
||||
return primaryStats
|
||||
}
|
||||
|
||||
func ReconcileRTPDeltaInfoWithRTX(primaryDeltaInfo *RTPDeltaInfo, rtxDeltaInfo *RTPDeltaInfo) *RTPDeltaInfo {
|
||||
if primaryDeltaInfo == nil || rtxDeltaInfo == nil {
|
||||
return primaryDeltaInfo
|
||||
}
|
||||
|
||||
primaryDeltaInfo.PacketsDuplicate += rtxDeltaInfo.Packets
|
||||
|
||||
primaryDeltaInfo.BytesDuplicate += rtxDeltaInfo.Bytes
|
||||
primaryDeltaInfo.HeaderBytesDuplicate += rtxDeltaInfo.HeaderBytes
|
||||
|
||||
primaryDeltaInfo.PacketsPadding += rtxDeltaInfo.PacketsPadding
|
||||
|
||||
primaryDeltaInfo.BytesPadding += rtxDeltaInfo.BytesPadding
|
||||
primaryDeltaInfo.HeaderBytesPadding += rtxDeltaInfo.HeaderBytesPadding
|
||||
|
||||
// RTX non-padding packets are responses to NACKs, that should discount packets lost
|
||||
lossAdjustment := rtxDeltaInfo.Packets - rtxDeltaInfo.PacketsLost - primaryDeltaInfo.NackRepeated
|
||||
if int32(lossAdjustment) < 0 {
|
||||
lossAdjustment = 0
|
||||
}
|
||||
if lossAdjustment >= primaryDeltaInfo.PacketsLost {
|
||||
primaryDeltaInfo.PacketsLost = 0
|
||||
} else {
|
||||
primaryDeltaInfo.PacketsLost -= lossAdjustment
|
||||
}
|
||||
return primaryDeltaInfo
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
@@ -0,0 +1,557 @@
|
||||
// Copyright 2023 LiveKit, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package rtpstats
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/livekit/protocol/livekit"
|
||||
"github.com/livekit/protocol/logger"
|
||||
"github.com/livekit/protocol/utils/mono"
|
||||
"go.uber.org/zap/zapcore"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
)
|
||||
|
||||
const (
|
||||
cGapHistogramNumBins = 101
|
||||
cNumSequenceNumbers = 65536
|
||||
cFirstSnapshotID = 1
|
||||
)
|
||||
|
||||
// -------------------------------------------------------
|
||||
|
||||
type RTPDeltaInfoLite struct {
|
||||
StartTime time.Time
|
||||
EndTime time.Time
|
||||
Packets uint32
|
||||
Bytes uint64
|
||||
PacketsLost uint32
|
||||
PacketsOutOfOrder uint32
|
||||
Nacks uint32
|
||||
}
|
||||
|
||||
func (r *RTPDeltaInfoLite) MarshalLogObject(e zapcore.ObjectEncoder) error {
|
||||
if r == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
e.AddTime("StartTime", r.StartTime)
|
||||
e.AddTime("EndTime", r.EndTime)
|
||||
e.AddUint32("Packets", r.Packets)
|
||||
e.AddUint64("Bytes", r.Bytes)
|
||||
e.AddUint32("PacketsLost", r.PacketsLost)
|
||||
e.AddUint32("PacketsOutOfOrder", r.PacketsOutOfOrder)
|
||||
e.AddUint32("Nacks", r.Nacks)
|
||||
return nil
|
||||
}
|
||||
|
||||
// -------------------------------------------------------
|
||||
|
||||
type snapshotLite struct {
|
||||
isValid bool
|
||||
|
||||
startTime int64
|
||||
|
||||
extStartSN uint64
|
||||
bytes uint64
|
||||
|
||||
packetsOutOfOrder uint64
|
||||
|
||||
packetsLost uint64
|
||||
|
||||
nacks uint32
|
||||
}
|
||||
|
||||
func (s *snapshotLite) MarshalLogObject(e zapcore.ObjectEncoder) error {
|
||||
if s == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
e.AddBool("isValid", s.isValid)
|
||||
e.AddTime("startTime", time.Unix(0, s.startTime))
|
||||
e.AddUint64("extStartSN", s.extStartSN)
|
||||
e.AddUint64("bytes", s.bytes)
|
||||
e.AddUint64("packetsOutOfOrder", s.packetsOutOfOrder)
|
||||
e.AddUint64("packetsLost", s.packetsLost)
|
||||
e.AddUint32("nacks", s.nacks)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
type RTPStatsParams struct {
|
||||
ClockRate uint32
|
||||
IsRTX bool
|
||||
Logger logger.Logger
|
||||
}
|
||||
|
||||
type rtpStatsBaseLite struct {
|
||||
params RTPStatsParams
|
||||
logger logger.Logger
|
||||
|
||||
lock sync.RWMutex
|
||||
|
||||
initialized bool
|
||||
|
||||
startTime int64
|
||||
endTime int64
|
||||
|
||||
bytes uint64
|
||||
|
||||
packetsOutOfOrder uint64
|
||||
|
||||
packetsLost uint64
|
||||
|
||||
gapHistogram [cGapHistogramNumBins]uint32
|
||||
|
||||
nacks uint32
|
||||
nackAcks uint32
|
||||
nackMisses uint32
|
||||
nackRepeated uint32
|
||||
|
||||
plis uint32
|
||||
lastPli int64
|
||||
|
||||
nextSnapshotLiteID uint32
|
||||
snapshotLites []snapshotLite
|
||||
}
|
||||
|
||||
func newRTPStatsBaseLite(params RTPStatsParams) *rtpStatsBaseLite {
|
||||
return &rtpStatsBaseLite{
|
||||
params: params,
|
||||
logger: params.Logger,
|
||||
nextSnapshotLiteID: cFirstSnapshotID,
|
||||
snapshotLites: make([]snapshotLite, 2),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *rtpStatsBaseLite) seed(from *rtpStatsBaseLite) bool {
|
||||
if from == nil || !from.initialized || r.initialized {
|
||||
return false
|
||||
}
|
||||
|
||||
r.initialized = from.initialized
|
||||
|
||||
r.startTime = from.startTime
|
||||
// do not clone endTime as a non-zero endTime indicates an ended object
|
||||
|
||||
r.bytes = from.bytes
|
||||
|
||||
r.packetsOutOfOrder = from.packetsOutOfOrder
|
||||
|
||||
r.packetsLost = from.packetsLost
|
||||
|
||||
r.gapHistogram = from.gapHistogram
|
||||
|
||||
r.nacks = from.nacks
|
||||
r.nackAcks = from.nackAcks
|
||||
r.nackMisses = from.nackMisses
|
||||
r.nackRepeated = from.nackRepeated
|
||||
|
||||
r.plis = from.plis
|
||||
r.lastPli = from.lastPli
|
||||
|
||||
r.nextSnapshotLiteID = from.nextSnapshotLiteID
|
||||
r.snapshotLites = make([]snapshotLite, cap(from.snapshotLites))
|
||||
copy(r.snapshotLites, from.snapshotLites)
|
||||
return true
|
||||
}
|
||||
|
||||
func (r *rtpStatsBaseLite) SetLogger(logger logger.Logger) {
|
||||
r.logger = logger
|
||||
}
|
||||
|
||||
func (r *rtpStatsBaseLite) Stop() {
|
||||
r.lock.Lock()
|
||||
defer r.lock.Unlock()
|
||||
|
||||
r.endTime = mono.UnixNano()
|
||||
}
|
||||
|
||||
func (r *rtpStatsBaseLite) newSnapshotLiteID(extStartSN uint64) uint32 {
|
||||
id := r.nextSnapshotLiteID
|
||||
r.nextSnapshotLiteID++
|
||||
|
||||
if cap(r.snapshotLites) < int(r.nextSnapshotLiteID-cFirstSnapshotID) {
|
||||
snapshotLites := make([]snapshotLite, r.nextSnapshotLiteID-cFirstSnapshotID)
|
||||
copy(snapshotLites, r.snapshotLites)
|
||||
r.snapshotLites = snapshotLites
|
||||
}
|
||||
|
||||
if r.initialized {
|
||||
r.snapshotLites[id-cFirstSnapshotID] = initSnapshotLite(mono.UnixNano(), extStartSN)
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
func (r *rtpStatsBaseLite) IsActive() bool {
|
||||
r.lock.RLock()
|
||||
defer r.lock.RUnlock()
|
||||
|
||||
return r.initialized && r.endTime == 0
|
||||
}
|
||||
|
||||
func (r *rtpStatsBaseLite) UpdateNack(nackCount uint32) {
|
||||
r.lock.Lock()
|
||||
defer r.lock.Unlock()
|
||||
|
||||
if r.endTime != 0 {
|
||||
return
|
||||
}
|
||||
|
||||
r.nacks += nackCount
|
||||
}
|
||||
|
||||
func (r *rtpStatsBaseLite) UpdateNackProcessed(nackAckCount uint32, nackMissCount uint32, nackRepeatedCount uint32) {
|
||||
r.lock.Lock()
|
||||
defer r.lock.Unlock()
|
||||
|
||||
if r.endTime != 0 {
|
||||
return
|
||||
}
|
||||
|
||||
r.nackAcks += nackAckCount
|
||||
r.nackMisses += nackMissCount
|
||||
r.nackRepeated += nackRepeatedCount
|
||||
}
|
||||
|
||||
func (r *rtpStatsBaseLite) CheckAndUpdatePli(throttle int64, force bool) bool {
|
||||
r.lock.Lock()
|
||||
defer r.lock.Unlock()
|
||||
|
||||
if r.endTime != 0 || (!force && mono.UnixNano()-r.lastPli < throttle) {
|
||||
return false
|
||||
}
|
||||
r.updatePliLocked(1)
|
||||
r.updatePliTimeLocked()
|
||||
return true
|
||||
}
|
||||
|
||||
func (r *rtpStatsBaseLite) UpdatePliAndTime(pliCount uint32) {
|
||||
r.lock.Lock()
|
||||
defer r.lock.Unlock()
|
||||
|
||||
if r.endTime != 0 {
|
||||
return
|
||||
}
|
||||
|
||||
r.updatePliLocked(pliCount)
|
||||
r.updatePliTimeLocked()
|
||||
}
|
||||
|
||||
func (r *rtpStatsBaseLite) UpdatePli(pliCount uint32) {
|
||||
r.lock.Lock()
|
||||
defer r.lock.Unlock()
|
||||
|
||||
if r.endTime != 0 {
|
||||
return
|
||||
}
|
||||
|
||||
r.updatePliLocked(pliCount)
|
||||
}
|
||||
|
||||
func (r *rtpStatsBaseLite) updatePliLocked(pliCount uint32) {
|
||||
r.plis += pliCount
|
||||
}
|
||||
|
||||
func (r *rtpStatsBaseLite) UpdatePliTime() {
|
||||
r.lock.Lock()
|
||||
defer r.lock.Unlock()
|
||||
|
||||
if r.endTime != 0 {
|
||||
return
|
||||
}
|
||||
|
||||
r.updatePliTimeLocked()
|
||||
}
|
||||
|
||||
func (r *rtpStatsBaseLite) updatePliTimeLocked() {
|
||||
r.lastPli = mono.UnixNano()
|
||||
}
|
||||
|
||||
func (r *rtpStatsBaseLite) LastPli() int64 {
|
||||
r.lock.RLock()
|
||||
defer r.lock.RUnlock()
|
||||
|
||||
return r.lastPli
|
||||
}
|
||||
|
||||
func (r *rtpStatsBaseLite) getPacketsSeen(extStartSN, extHighestSN uint64) uint64 {
|
||||
packetsExpected := getPacketsExpected(extStartSN, extHighestSN)
|
||||
if r.packetsLost > packetsExpected {
|
||||
// should not happen
|
||||
return 0
|
||||
}
|
||||
|
||||
return packetsExpected - r.packetsLost
|
||||
}
|
||||
|
||||
func (r *rtpStatsBaseLite) deltaInfoLite(
|
||||
snapshotLiteID uint32,
|
||||
extStartSN uint64,
|
||||
extHighestSN uint64,
|
||||
) (deltaInfoLite *RTPDeltaInfoLite, err error, loggingFields []interface{}) {
|
||||
then, now := r.getAndResetSnapshotLite(snapshotLiteID, extStartSN, extHighestSN)
|
||||
if now == nil || then == nil {
|
||||
return
|
||||
}
|
||||
|
||||
startTime := then.startTime
|
||||
endTime := now.startTime
|
||||
|
||||
packetsExpected := uint32(now.extStartSN - then.extStartSN)
|
||||
if then.extStartSN > extHighestSN {
|
||||
packetsExpected = 0
|
||||
}
|
||||
if packetsExpected > cNumSequenceNumbers {
|
||||
loggingFields = []interface{}{
|
||||
"snapshotLiteID", snapshotLiteID,
|
||||
"snapshotLiteNow", now,
|
||||
"snapshotLiteThen", then,
|
||||
"packetsExpected", packetsExpected,
|
||||
"duration", time.Duration(endTime - startTime),
|
||||
}
|
||||
err = errors.New("too many packets expected in delta lite")
|
||||
return
|
||||
}
|
||||
if packetsExpected == 0 {
|
||||
deltaInfoLite = &RTPDeltaInfoLite{
|
||||
StartTime: time.Unix(0, startTime),
|
||||
EndTime: time.Unix(0, endTime),
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
packetsLost := uint32(now.packetsLost - then.packetsLost)
|
||||
if int32(packetsLost) < 0 {
|
||||
packetsLost = 0
|
||||
}
|
||||
if packetsLost > packetsExpected {
|
||||
loggingFields = []interface{}{
|
||||
"snapshotLiteID", snapshotLiteID,
|
||||
"snapshotLiteNow", now,
|
||||
"snapshotLiteThen", then,
|
||||
"packetsExpected", packetsExpected,
|
||||
"packetsLost", packetsLost,
|
||||
"duration", time.Duration(endTime - startTime),
|
||||
}
|
||||
err = errors.New("unexpected number of packets lost in delta lite")
|
||||
}
|
||||
|
||||
deltaInfoLite = &RTPDeltaInfoLite{
|
||||
StartTime: time.Unix(0, startTime),
|
||||
EndTime: time.Unix(0, endTime),
|
||||
Packets: packetsExpected,
|
||||
Bytes: now.bytes - then.bytes,
|
||||
PacketsLost: packetsLost,
|
||||
PacketsOutOfOrder: uint32(now.packetsOutOfOrder - then.packetsOutOfOrder),
|
||||
Nacks: now.nacks - then.nacks,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (r *rtpStatsBaseLite) marshalLogObject(e zapcore.ObjectEncoder, packetsExpected, packetsSeenMinusPadding uint64) (float64, error) {
|
||||
if r == nil || !r.initialized {
|
||||
return 0, errors.New("not initialized")
|
||||
}
|
||||
|
||||
endTime := r.endTime
|
||||
if endTime == 0 {
|
||||
endTime = mono.UnixNano()
|
||||
}
|
||||
elapsed := time.Duration(endTime - r.startTime)
|
||||
if elapsed == 0 {
|
||||
return 0, errors.New("no time elapsed")
|
||||
}
|
||||
elapsedSeconds := elapsed.Seconds()
|
||||
|
||||
e.AddTime("startTime", time.Unix(0, r.startTime))
|
||||
e.AddTime("endTime", time.Unix(0, r.endTime))
|
||||
e.AddDuration("elapsed", elapsed)
|
||||
|
||||
e.AddUint64("packetsExpected", packetsExpected)
|
||||
e.AddFloat64("packetsExpectedRate", float64(packetsExpected)/elapsedSeconds)
|
||||
e.AddUint64("packetsSeenPrimary", packetsSeenMinusPadding)
|
||||
e.AddFloat64("packetsSeenPrimaryRate", float64(packetsSeenMinusPadding)/elapsedSeconds)
|
||||
e.AddUint64("bytes", r.bytes)
|
||||
e.AddFloat64("bitrate", float64(r.bytes)*8.0/elapsedSeconds)
|
||||
|
||||
e.AddUint64("packetsOutOfOrder", r.packetsOutOfOrder)
|
||||
|
||||
e.AddUint64("packetsLost", r.packetsLost)
|
||||
e.AddFloat64("packetsLostRate", float64(r.packetsLost)/elapsedSeconds)
|
||||
if packetsExpected != 0 {
|
||||
e.AddFloat32("packetLostPercentage", float32(r.packetsLost)/float32(packetsExpected)*100.0)
|
||||
}
|
||||
|
||||
hasLoss := false
|
||||
first := true
|
||||
str := "["
|
||||
for burst, count := range r.gapHistogram {
|
||||
if count == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
hasLoss = true
|
||||
|
||||
if !first {
|
||||
str += ", "
|
||||
}
|
||||
first = false
|
||||
str += fmt.Sprintf("%d:%d", burst+1, count)
|
||||
}
|
||||
str += "]"
|
||||
if hasLoss {
|
||||
e.AddString("gapHistogram", str)
|
||||
}
|
||||
|
||||
e.AddUint32("nacks", r.nacks)
|
||||
e.AddUint32("nackAcks", r.nackAcks)
|
||||
e.AddUint32("nackMisses", r.nackMisses)
|
||||
e.AddUint32("nackRepeated", r.nackRepeated)
|
||||
|
||||
e.AddUint32("plis", r.plis)
|
||||
e.AddTime("lastPli", time.Unix(0, r.lastPli))
|
||||
return elapsedSeconds, nil
|
||||
}
|
||||
|
||||
func (r *rtpStatsBaseLite) toProto(packetsExpected, packetsSeenMinusPadding, packetsLost uint64) *livekit.RTPStats {
|
||||
if r.startTime == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
endTime := r.endTime
|
||||
if endTime == 0 {
|
||||
endTime = mono.UnixNano()
|
||||
}
|
||||
elapsed := time.Duration(endTime - r.startTime).Seconds()
|
||||
if elapsed == 0.0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
packetRate := float64(packetsSeenMinusPadding) / elapsed
|
||||
bitrate := float64(r.bytes) * 8.0 / elapsed
|
||||
|
||||
packetLostRate := float64(packetsLost) / elapsed
|
||||
packetLostPercentage := float32(0)
|
||||
if packetsExpected != 0 {
|
||||
packetLostPercentage = float32(packetsLost) / float32(packetsExpected) * 100.0
|
||||
}
|
||||
|
||||
p := &livekit.RTPStats{
|
||||
StartTime: timestamppb.New(time.Unix(0, r.startTime)),
|
||||
EndTime: timestamppb.New(time.Unix(0, endTime)),
|
||||
Duration: elapsed,
|
||||
Packets: uint32(packetsSeenMinusPadding),
|
||||
PacketRate: packetRate,
|
||||
Bytes: r.bytes,
|
||||
Bitrate: bitrate,
|
||||
PacketsLost: uint32(packetsLost),
|
||||
PacketLossRate: packetLostRate,
|
||||
PacketLossPercentage: packetLostPercentage,
|
||||
PacketsOutOfOrder: uint32(r.packetsOutOfOrder),
|
||||
Nacks: r.nacks,
|
||||
NackAcks: r.nackAcks,
|
||||
NackMisses: r.nackMisses,
|
||||
NackRepeated: r.nackRepeated,
|
||||
Plis: r.plis,
|
||||
LastPli: timestamppb.New(time.Unix(0, r.lastPli)),
|
||||
}
|
||||
|
||||
gapsPresent := false
|
||||
for i := 0; i < len(r.gapHistogram); i++ {
|
||||
if r.gapHistogram[i] == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
gapsPresent = true
|
||||
break
|
||||
}
|
||||
|
||||
if gapsPresent {
|
||||
p.GapHistogram = make(map[int32]uint32, len(r.gapHistogram))
|
||||
for i := 0; i < len(r.gapHistogram); i++ {
|
||||
if r.gapHistogram[i] == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
p.GapHistogram[int32(i+1)] = r.gapHistogram[i]
|
||||
}
|
||||
}
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
func (r *rtpStatsBaseLite) getAndResetSnapshotLite(snapshotLiteID uint32, extStartSN uint64, extHighestSN uint64) (*snapshotLite, *snapshotLite) {
|
||||
if !r.initialized {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
idx := snapshotLiteID - cFirstSnapshotID
|
||||
then := r.snapshotLites[idx]
|
||||
if !then.isValid {
|
||||
then = initSnapshotLite(r.startTime, extStartSN)
|
||||
r.snapshotLites[idx] = then
|
||||
}
|
||||
|
||||
// snapshot now
|
||||
now := r.getSnapshotLite(mono.UnixNano(), extHighestSN+1)
|
||||
r.snapshotLites[idx] = now
|
||||
return &then, &now
|
||||
}
|
||||
|
||||
func (r *rtpStatsBaseLite) updateGapHistogram(gap int) {
|
||||
if gap < 2 {
|
||||
return
|
||||
}
|
||||
|
||||
missing := gap - 1
|
||||
if missing > len(r.gapHistogram) {
|
||||
r.gapHistogram[len(r.gapHistogram)-1]++
|
||||
} else {
|
||||
r.gapHistogram[missing-1]++
|
||||
}
|
||||
}
|
||||
|
||||
func (r *rtpStatsBaseLite) getSnapshotLite(startTime int64, extStartSN uint64) snapshotLite {
|
||||
return snapshotLite{
|
||||
isValid: true,
|
||||
startTime: startTime,
|
||||
extStartSN: extStartSN,
|
||||
bytes: r.bytes,
|
||||
packetsOutOfOrder: r.packetsOutOfOrder,
|
||||
packetsLost: r.packetsLost,
|
||||
nacks: r.nacks,
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------
|
||||
|
||||
func initSnapshotLite(startTime int64, extStartSN uint64) snapshotLite {
|
||||
return snapshotLite{
|
||||
isValid: true,
|
||||
startTime: startTime,
|
||||
extStartSN: extStartSN,
|
||||
}
|
||||
}
|
||||
|
||||
func getPacketsExpected(extStartSN, extHighestSN uint64) uint64 {
|
||||
return extHighestSN - extStartSN + 1
|
||||
}
|
||||
|
||||
// ----------------------------------
|
||||
@@ -0,0 +1,731 @@
|
||||
// Copyright 2023 LiveKit, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package rtpstats
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"github.com/pion/rtcp"
|
||||
"go.uber.org/zap/zapcore"
|
||||
|
||||
"github.com/livekit/livekit-server/pkg/sfu/utils"
|
||||
"github.com/livekit/mediatransportutil"
|
||||
"github.com/livekit/protocol/livekit"
|
||||
"github.com/livekit/protocol/logger"
|
||||
protoutils "github.com/livekit/protocol/utils"
|
||||
"github.com/livekit/protocol/utils/mono"
|
||||
)
|
||||
|
||||
const (
|
||||
cHistorySize = 8192
|
||||
|
||||
// number of seconds the current report RTP timestamp can be off from expected RTP timestamp
|
||||
cReportSlack = float64(60.0)
|
||||
|
||||
cTSJumpTooHighFactor = float64(1.5)
|
||||
)
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
type RTPFlowState struct {
|
||||
IsNotHandled bool
|
||||
|
||||
LossStartInclusive uint64
|
||||
LossEndExclusive uint64
|
||||
|
||||
IsDuplicate bool
|
||||
IsOutOfOrder bool
|
||||
|
||||
ExtSequenceNumber uint64
|
||||
ExtTimestamp uint64
|
||||
}
|
||||
|
||||
func (r *RTPFlowState) MarshalLogObject(e zapcore.ObjectEncoder) error {
|
||||
if r == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
e.AddBool("IsNotHandled", r.IsNotHandled)
|
||||
e.AddUint64("LossStartInclusive", r.LossStartInclusive)
|
||||
e.AddUint64("LossEndExclusive", r.LossEndExclusive)
|
||||
e.AddBool("IsDuplicate", r.IsDuplicate)
|
||||
e.AddBool("IsOutOfOrder", r.IsOutOfOrder)
|
||||
e.AddUint64("ExtSequenceNumber", r.ExtSequenceNumber)
|
||||
e.AddUint64("ExtTimestamp", r.ExtTimestamp)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
type RTPStatsReceiver struct {
|
||||
*rtpStatsBase
|
||||
|
||||
sequenceNumber *utils.WrapAround[uint16, uint64]
|
||||
|
||||
tsRolloverThreshold int64
|
||||
timestamp *utils.WrapAround[uint32, uint64]
|
||||
|
||||
history *protoutils.Bitmap[uint64]
|
||||
|
||||
propagationDelayEstimator *utils.OWDEstimator
|
||||
|
||||
clockSkewCount int
|
||||
clockSkewMediaPathCount int
|
||||
outOfOrderSenderReportCount int
|
||||
largeJumpCount int
|
||||
largeJumpNegativeCount int
|
||||
timeReversedCount int
|
||||
}
|
||||
|
||||
func NewRTPStatsReceiver(params RTPStatsParams) *RTPStatsReceiver {
|
||||
return &RTPStatsReceiver{
|
||||
rtpStatsBase: newRTPStatsBase(params),
|
||||
sequenceNumber: utils.NewWrapAround[uint16, uint64](utils.WrapAroundParams{IsRestartAllowed: false}),
|
||||
tsRolloverThreshold: (1 << 31) * 1e9 / int64(params.ClockRate),
|
||||
timestamp: utils.NewWrapAround[uint32, uint64](utils.WrapAroundParams{IsRestartAllowed: false}),
|
||||
history: protoutils.NewBitmap[uint64](cHistorySize),
|
||||
propagationDelayEstimator: utils.NewOWDEstimator(utils.OWDEstimatorParamsDefault),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *RTPStatsReceiver) NewSnapshotId() uint32 {
|
||||
r.lock.Lock()
|
||||
defer r.lock.Unlock()
|
||||
|
||||
return r.newSnapshotID(r.sequenceNumber.GetExtendedHighest())
|
||||
}
|
||||
|
||||
func (r *RTPStatsReceiver) getTSRolloverCount(diffNano int64, ts uint32) int {
|
||||
if diffNano < r.tsRolloverThreshold {
|
||||
// time not more than rollover threshold
|
||||
return -1
|
||||
}
|
||||
|
||||
excess := (diffNano - r.tsRolloverThreshold*2) * int64(r.params.ClockRate) / 1e9
|
||||
roc := excess / (1 << 32)
|
||||
if roc < 0 {
|
||||
roc = 0
|
||||
}
|
||||
if r.timestamp.GetHighest() > ts {
|
||||
roc++
|
||||
}
|
||||
return int(roc)
|
||||
}
|
||||
|
||||
func (r *RTPStatsReceiver) Update(
|
||||
packetTime int64,
|
||||
sequenceNumber uint16,
|
||||
timestamp uint32,
|
||||
marker bool,
|
||||
hdrSize int,
|
||||
payloadSize int,
|
||||
paddingSize int,
|
||||
) (flowState RTPFlowState) {
|
||||
r.lock.Lock()
|
||||
defer r.lock.Unlock()
|
||||
|
||||
if r.endTime != 0 {
|
||||
flowState.IsNotHandled = true
|
||||
return
|
||||
}
|
||||
|
||||
var resSN utils.WrapAroundUpdateResult[uint64]
|
||||
var gapSN int64
|
||||
var resTS utils.WrapAroundUpdateResult[uint64]
|
||||
var gapTS int64
|
||||
var expectedTSJump int64
|
||||
var timeSinceHighest int64
|
||||
var tsRolloverCount int
|
||||
var snRolloverCount int
|
||||
|
||||
logger := func() logger.UnlikelyLogger {
|
||||
return r.logger.WithUnlikelyValues(
|
||||
"resSN", resSN,
|
||||
"gapSN", gapSN,
|
||||
"resTS", resTS,
|
||||
"gapTS", gapTS,
|
||||
"snRolloverCount", snRolloverCount,
|
||||
"expectedTSJump", expectedTSJump,
|
||||
"tsRolloverCount", tsRolloverCount,
|
||||
"packetTime", time.Unix(0, packetTime),
|
||||
"timeSinceHighest", time.Duration(timeSinceHighest),
|
||||
"sequenceNumber", sequenceNumber,
|
||||
"timestamp", timestamp,
|
||||
"marker", marker,
|
||||
"hdrSize", hdrSize,
|
||||
"payloadSize", payloadSize,
|
||||
"paddingSize", paddingSize,
|
||||
"rtpStats", lockedRTPStatsReceiverLogEncoder{r},
|
||||
)
|
||||
}
|
||||
|
||||
if !r.initialized {
|
||||
if payloadSize == 0 {
|
||||
// do not start on a padding only packet
|
||||
flowState.IsNotHandled = true
|
||||
return
|
||||
}
|
||||
|
||||
r.initialized = true
|
||||
|
||||
r.startTime = mono.UnixNano()
|
||||
|
||||
r.firstTime = packetTime
|
||||
r.highestTime = packetTime
|
||||
|
||||
resSN = r.sequenceNumber.Update(sequenceNumber)
|
||||
resTS = r.timestamp.Update(timestamp)
|
||||
|
||||
// initialize snapshots if any
|
||||
for i := uint32(0); i < r.nextSnapshotID-cFirstSnapshotID; i++ {
|
||||
r.snapshots[i] = initSnapshot(r.startTime, r.sequenceNumber.GetExtendedStart())
|
||||
}
|
||||
|
||||
r.logger.Debugw(
|
||||
"rtp receiver stream start",
|
||||
"rtpStats", lockedRTPStatsReceiverLogEncoder{r},
|
||||
)
|
||||
} else {
|
||||
resSN = r.sequenceNumber.Update(sequenceNumber)
|
||||
if resSN.IsUnhandled {
|
||||
flowState.IsNotHandled = true
|
||||
return
|
||||
}
|
||||
gapSN = int64(resSN.ExtendedVal - resSN.PreExtendedHighest)
|
||||
|
||||
timeSinceHighest = packetTime - r.highestTime
|
||||
tsRolloverCount = r.getTSRolloverCount(timeSinceHighest, timestamp)
|
||||
if tsRolloverCount >= 0 {
|
||||
logger().Warnw("potential time stamp roll over", nil)
|
||||
}
|
||||
resTS = r.timestamp.Rollover(timestamp, tsRolloverCount)
|
||||
if resTS.IsUnhandled {
|
||||
flowState.IsNotHandled = true
|
||||
return
|
||||
}
|
||||
gapTS = int64(resTS.ExtendedVal - resTS.PreExtendedHighest)
|
||||
|
||||
// it is possible to receive old packets in two different scenarios
|
||||
// as it is not possible to detect how far to roll back, ignore old packets
|
||||
//
|
||||
// Case 1:
|
||||
// Very old time stamp, happens under the following conditions
|
||||
// - resume after long mute, big time stamp jump
|
||||
// - an out of order packet from before the mute arrives (unsure what causes this
|
||||
// very old packet to be trasmitted from remote), causing time stamp to jump back
|
||||
// to before mute, but it appears like it has rolled over.
|
||||
// Use a threshold against expected to ignore these.
|
||||
if gapSN < 0 && gapTS > 0 {
|
||||
expectedTSJump = timeSinceHighest * int64(r.params.ClockRate) / 1e9
|
||||
if gapTS > int64(float64(expectedTSJump)*cTSJumpTooHighFactor) {
|
||||
r.sequenceNumber.UndoUpdate(resSN)
|
||||
r.timestamp.UndoUpdate(resTS)
|
||||
logger().Warnw("dropping old packet, timestamp", nil)
|
||||
flowState.IsNotHandled = true
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Case 2:
|
||||
// Sequence number looks like it is moving forward, but it is actually a very old packet.
|
||||
if gapTS < 0 && gapSN > 0 {
|
||||
r.sequenceNumber.UndoUpdate(resSN)
|
||||
r.timestamp.UndoUpdate(resTS)
|
||||
logger().Warnw("dropping old packet, sequence number", nil)
|
||||
flowState.IsNotHandled = true
|
||||
return
|
||||
}
|
||||
|
||||
// it is possible that sequence number has rolled over too
|
||||
if gapSN < 0 && gapTS > 0 && payloadSize > 0 {
|
||||
// not possible to know how many cycles of sequence number roll over could have happened,
|
||||
// ensure that it at least does not go backwards
|
||||
snRolloverCount = 0
|
||||
if sequenceNumber < r.sequenceNumber.GetHighest() {
|
||||
snRolloverCount = 1
|
||||
}
|
||||
resSN = r.sequenceNumber.Rollover(sequenceNumber, snRolloverCount)
|
||||
if resSN.IsUnhandled {
|
||||
flowState.IsNotHandled = true
|
||||
return
|
||||
}
|
||||
|
||||
logger().Warnw("forcing sequence number rollover", nil)
|
||||
}
|
||||
}
|
||||
gapSN = int64(resSN.ExtendedVal - resSN.PreExtendedHighest)
|
||||
|
||||
pktSize := uint64(hdrSize + payloadSize + paddingSize)
|
||||
if gapSN <= 0 { // duplicate OR out-of-order
|
||||
if gapSN != 0 {
|
||||
r.packetsOutOfOrder++
|
||||
}
|
||||
|
||||
if r.isInRange(resSN.ExtendedVal, resSN.PreExtendedHighest) {
|
||||
if r.history.GetAndSet(resSN.ExtendedVal) {
|
||||
r.bytesDuplicate += pktSize
|
||||
r.headerBytesDuplicate += uint64(hdrSize)
|
||||
r.packetsDuplicate++
|
||||
flowState.IsDuplicate = true
|
||||
} else {
|
||||
r.packetsLost--
|
||||
}
|
||||
}
|
||||
|
||||
flowState.IsOutOfOrder = true
|
||||
|
||||
if !flowState.IsDuplicate && -gapSN >= cSequenceNumberLargeJumpThreshold {
|
||||
r.largeJumpNegativeCount++
|
||||
if (r.largeJumpNegativeCount-1)%100 == 0 {
|
||||
logger().Warnw(
|
||||
"large sequence number gap negative", nil,
|
||||
"count", r.largeJumpNegativeCount,
|
||||
)
|
||||
}
|
||||
}
|
||||
} else { // in-order
|
||||
if gapSN >= cSequenceNumberLargeJumpThreshold {
|
||||
r.largeJumpCount++
|
||||
if (r.largeJumpCount-1)%100 == 0 {
|
||||
logger().Warnw(
|
||||
"large sequence number gap", nil,
|
||||
"count", r.largeJumpCount,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if resTS.ExtendedVal < resTS.PreExtendedHighest {
|
||||
r.timeReversedCount++
|
||||
if (r.timeReversedCount-1)%100 == 0 {
|
||||
logger().Warnw(
|
||||
"time reversed", nil,
|
||||
"count", r.timeReversedCount,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// update gap histogram
|
||||
r.updateGapHistogram(int(gapSN))
|
||||
|
||||
// update missing sequence numbers
|
||||
r.history.ClearRange(resSN.PreExtendedHighest+1, resSN.ExtendedVal-1)
|
||||
r.packetsLost += uint64(gapSN - 1)
|
||||
|
||||
r.history.Set(resSN.ExtendedVal)
|
||||
|
||||
if timestamp != uint32(resTS.PreExtendedHighest) {
|
||||
// update only on first packet as same timestamp could be in multiple packets.
|
||||
// NOTE: this may not be the first packet with this time stamp if there is packet loss.
|
||||
r.highestTime = packetTime
|
||||
}
|
||||
|
||||
flowState.LossStartInclusive = resSN.PreExtendedHighest + 1
|
||||
flowState.LossEndExclusive = resSN.ExtendedVal
|
||||
}
|
||||
flowState.ExtSequenceNumber = resSN.ExtendedVal
|
||||
flowState.ExtTimestamp = resTS.ExtendedVal
|
||||
|
||||
if !flowState.IsDuplicate {
|
||||
if payloadSize == 0 {
|
||||
r.packetsPadding++
|
||||
r.bytesPadding += pktSize
|
||||
r.headerBytesPadding += uint64(hdrSize)
|
||||
} else {
|
||||
r.bytes += pktSize
|
||||
r.headerBytes += uint64(hdrSize)
|
||||
|
||||
if marker {
|
||||
r.frames++
|
||||
}
|
||||
|
||||
r.updateJitter(resTS.ExtendedVal, packetTime)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (r *RTPStatsReceiver) getExtendedSenderReport(srData *livekit.RTCPSenderReportState) *livekit.RTCPSenderReportState {
|
||||
tsCycles := uint64(0)
|
||||
if r.srNewest != nil {
|
||||
// use time since last sender report to ensure long gaps where the time stamp might
|
||||
// jump more than half the range
|
||||
timeSinceLastReport := mediatransportutil.NtpTime(srData.NtpTimestamp).Time().Sub(mediatransportutil.NtpTime(r.srNewest.NtpTimestamp).Time())
|
||||
expectedRTPTimestampExt := r.srNewest.RtpTimestampExt + uint64(timeSinceLastReport.Nanoseconds()*int64(r.params.ClockRate)/1e9)
|
||||
lbound := expectedRTPTimestampExt - uint64(cReportSlack*float64(r.params.ClockRate))
|
||||
ubound := expectedRTPTimestampExt + uint64(cReportSlack*float64(r.params.ClockRate))
|
||||
isInRange := (srData.RtpTimestamp-uint32(lbound) < (1 << 31)) && (uint32(ubound)-srData.RtpTimestamp < (1 << 31))
|
||||
if isInRange {
|
||||
lbTSCycles := lbound & 0xFFFF_FFFF_0000_0000
|
||||
ubTSCycles := ubound & 0xFFFF_FFFF_0000_0000
|
||||
if lbTSCycles == ubTSCycles {
|
||||
tsCycles = lbTSCycles
|
||||
} else {
|
||||
if srData.RtpTimestamp < (1 << 31) {
|
||||
// rolled over
|
||||
tsCycles = ubTSCycles
|
||||
} else {
|
||||
tsCycles = lbTSCycles
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// ideally this method should not be required, but there are clients
|
||||
// negotiating one clock rate, but actually send media at a different rate.
|
||||
tsCycles = r.srNewest.RtpTimestampExt & 0xFFFF_FFFF_0000_0000
|
||||
if (srData.RtpTimestamp-r.srNewest.RtpTimestamp) < (1<<31) && srData.RtpTimestamp < r.srNewest.RtpTimestamp {
|
||||
tsCycles += (1 << 32)
|
||||
}
|
||||
|
||||
if tsCycles >= (1 << 32) {
|
||||
if (srData.RtpTimestamp-r.srNewest.RtpTimestamp) >= (1<<31) && srData.RtpTimestamp > r.srNewest.RtpTimestamp {
|
||||
tsCycles -= (1 << 32)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
srDataExt := protoutils.CloneProto(srData)
|
||||
srDataExt.RtpTimestampExt = uint64(srDataExt.RtpTimestamp) + tsCycles
|
||||
return srDataExt
|
||||
}
|
||||
|
||||
func (r *RTPStatsReceiver) checkOutOfOrderSenderReport(srData *livekit.RTCPSenderReportState) bool {
|
||||
if r.srNewest != nil && srData.RtpTimestampExt < r.srNewest.RtpTimestampExt {
|
||||
// This can happen when a track is replaced with a null and then restored -
|
||||
// i. e. muting replacing with null and unmute restoring the original track.
|
||||
// Or it could be due bad report generation.
|
||||
// In any case, ignore out-of-order reports.
|
||||
r.outOfOrderSenderReportCount++
|
||||
if (r.outOfOrderSenderReportCount-1)%10 == 0 {
|
||||
r.logger.Infow(
|
||||
"received sender report, out-of-order, skipping",
|
||||
"current", WrappedRTCPSenderReportStateLogger{srData},
|
||||
"count", r.outOfOrderSenderReportCount,
|
||||
"rtpStats", lockedRTPStatsReceiverLogEncoder{r},
|
||||
)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (r *RTPStatsReceiver) checkRTPClockSkewForSenderReport(srData *livekit.RTCPSenderReportState) {
|
||||
if r.srNewest == nil {
|
||||
return
|
||||
}
|
||||
|
||||
timeSinceLast := mediatransportutil.NtpTime(srData.NtpTimestamp).Time().Sub(mediatransportutil.NtpTime(r.srNewest.NtpTimestamp).Time()).Seconds()
|
||||
rtpDiffSinceLast := srData.RtpTimestampExt - r.srNewest.RtpTimestampExt
|
||||
calculatedClockRateFromLast := float64(rtpDiffSinceLast) / timeSinceLast
|
||||
|
||||
timeSinceFirst := mediatransportutil.NtpTime(srData.NtpTimestamp).Time().Sub(mediatransportutil.NtpTime(r.srFirst.NtpTimestamp).Time()).Seconds()
|
||||
rtpDiffSinceFirst := srData.RtpTimestampExt - r.srFirst.RtpTimestampExt
|
||||
calculatedClockRateFromFirst := float64(rtpDiffSinceFirst) / timeSinceFirst
|
||||
|
||||
if (timeSinceLast > 0.2 && math.Abs(float64(r.params.ClockRate)-calculatedClockRateFromLast) > 0.2*float64(r.params.ClockRate)) ||
|
||||
(timeSinceFirst > 0.2 && math.Abs(float64(r.params.ClockRate)-calculatedClockRateFromFirst) > 0.2*float64(r.params.ClockRate)) {
|
||||
r.clockSkewCount++
|
||||
if (r.clockSkewCount-1)%100 == 0 {
|
||||
r.logger.Infow(
|
||||
"received sender report, clock skew",
|
||||
"current", WrappedRTCPSenderReportStateLogger{srData},
|
||||
"timeSinceFirst", timeSinceFirst,
|
||||
"rtpDiffSinceFirst", rtpDiffSinceFirst,
|
||||
"calculatedFirst", calculatedClockRateFromFirst,
|
||||
"timeSinceLast", timeSinceLast,
|
||||
"rtpDiffSinceLast", rtpDiffSinceLast,
|
||||
"calculatedLast", calculatedClockRateFromLast,
|
||||
"count", r.clockSkewCount,
|
||||
"rtpStats", lockedRTPStatsReceiverLogEncoder{r},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *RTPStatsReceiver) checkRTPClockSkewAgainstMediaPathForSenderReport(srData *livekit.RTCPSenderReportState) {
|
||||
if r.highestTime == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
nowNano := mono.UnixNano()
|
||||
timeSinceSR := time.Duration(nowNano - srData.AtAdjusted)
|
||||
extNowTSSR := srData.RtpTimestampExt + uint64(timeSinceSR.Nanoseconds()*int64(r.params.ClockRate)/1e9)
|
||||
|
||||
timeSinceHighest := time.Duration(nowNano - r.highestTime)
|
||||
extNowTSHighest := r.timestamp.GetExtendedHighest() + uint64(timeSinceHighest.Nanoseconds()*int64(r.params.ClockRate)/1e9)
|
||||
diffHighest := extNowTSSR - extNowTSHighest
|
||||
|
||||
timeSinceFirst := time.Duration(nowNano - r.firstTime)
|
||||
extNowTSFirst := r.timestamp.GetExtendedStart() + uint64(timeSinceFirst.Nanoseconds()*int64(r.params.ClockRate)/1e9)
|
||||
diffFirst := extNowTSSR - extNowTSFirst
|
||||
|
||||
// is it more than 5 seconds off?
|
||||
if uint32(math.Abs(float64(int64(diffHighest)))) > 5*r.params.ClockRate || uint32(math.Abs(float64(int64(diffFirst)))) > 5*r.params.ClockRate {
|
||||
r.clockSkewMediaPathCount++
|
||||
if (r.clockSkewMediaPathCount-1)%100 == 0 {
|
||||
r.logger.Infow(
|
||||
"received sender report, clock skew against media path",
|
||||
"current", WrappedRTCPSenderReportStateLogger{srData},
|
||||
"timeSinceSR", timeSinceSR,
|
||||
"extNowTSSR", extNowTSSR,
|
||||
"timeSinceHighest", timeSinceHighest,
|
||||
"extNowTSHighest", extNowTSHighest,
|
||||
"diffHighest", int64(diffHighest),
|
||||
"timeSinceFirst", timeSinceFirst,
|
||||
"extNowTSFirst", extNowTSFirst,
|
||||
"diffFirst", int64(diffFirst),
|
||||
"count", r.clockSkewMediaPathCount,
|
||||
"rtpStats", lockedRTPStatsReceiverLogEncoder{r},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *RTPStatsReceiver) updatePropagationDelayAndRecordSenderReport(srData *livekit.RTCPSenderReportState) {
|
||||
senderClockTime := mediatransportutil.NtpTime(srData.NtpTimestamp).Time().UnixNano()
|
||||
estimatedPropagationDelay, stepChange := r.propagationDelayEstimator.Update(senderClockTime, srData.At)
|
||||
if stepChange {
|
||||
r.logger.Debugw(
|
||||
"propagation delay step change",
|
||||
"currentSenderReport", WrappedRTCPSenderReportStateLogger{srData},
|
||||
"rtpStats", lockedRTPStatsReceiverLogEncoder{r},
|
||||
)
|
||||
}
|
||||
|
||||
if r.srFirst == nil {
|
||||
r.srFirst = srData
|
||||
}
|
||||
// adjust receive time to estimated propagation delay
|
||||
srData.AtAdjusted = senderClockTime + estimatedPropagationDelay
|
||||
r.srNewest = srData
|
||||
}
|
||||
|
||||
func (r *RTPStatsReceiver) SetRtcpSenderReportData(srData *livekit.RTCPSenderReportState) bool {
|
||||
r.lock.Lock()
|
||||
defer r.lock.Unlock()
|
||||
|
||||
if srData == nil || !r.initialized {
|
||||
return false
|
||||
}
|
||||
|
||||
// prevent against extreme case of anachronous sender reports
|
||||
if r.srNewest != nil && r.srNewest.NtpTimestamp > srData.NtpTimestamp {
|
||||
r.logger.Infow(
|
||||
"received sender report, anachronous, dropping",
|
||||
"current", WrappedRTCPSenderReportStateLogger{srData},
|
||||
"rtpStats", lockedRTPStatsReceiverLogEncoder{r},
|
||||
)
|
||||
return false
|
||||
}
|
||||
|
||||
srDataExt := r.getExtendedSenderReport(srData)
|
||||
|
||||
if r.checkOutOfOrderSenderReport(srDataExt) {
|
||||
return false
|
||||
}
|
||||
|
||||
r.checkRTPClockSkewForSenderReport(srDataExt)
|
||||
r.updatePropagationDelayAndRecordSenderReport(srDataExt)
|
||||
r.checkRTPClockSkewAgainstMediaPathForSenderReport(srDataExt)
|
||||
|
||||
if err, loggingFields := r.maybeAdjustFirstPacketTime(r.srNewest, 0, r.timestamp.GetExtendedStart()); err != nil {
|
||||
r.logger.Infow(err.Error(), append(loggingFields, "rtpStats", lockedRTPStatsReceiverLogEncoder{r})...)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (r *RTPStatsReceiver) GetRtcpSenderReportData() *livekit.RTCPSenderReportState {
|
||||
r.lock.RLock()
|
||||
defer r.lock.RUnlock()
|
||||
|
||||
return protoutils.CloneProto(r.srNewest)
|
||||
}
|
||||
|
||||
func (r *RTPStatsReceiver) LastSenderReportTime() time.Time {
|
||||
r.lock.RLock()
|
||||
defer r.lock.RUnlock()
|
||||
|
||||
if r.srNewest != nil {
|
||||
return time.Unix(0, r.srNewest.At)
|
||||
}
|
||||
|
||||
return time.Time{}
|
||||
}
|
||||
|
||||
func (r *RTPStatsReceiver) GetRtcpReceptionReport(ssrc uint32, proxyFracLost uint8, snapshotID uint32) *rtcp.ReceptionReport {
|
||||
r.lock.Lock()
|
||||
defer r.lock.Unlock()
|
||||
|
||||
extHighestSN := r.sequenceNumber.GetExtendedHighest()
|
||||
then, now := r.getAndResetSnapshot(snapshotID, r.sequenceNumber.GetExtendedStart(), extHighestSN)
|
||||
if now == nil || then == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
packetsExpected := now.extStartSN - then.extStartSN
|
||||
if packetsExpected > cNumSequenceNumbers {
|
||||
r.logger.Warnw(
|
||||
"too many packets expected in receiver report",
|
||||
fmt.Errorf("start: %d, end: %d, expected: %d", then.extStartSN, now.extStartSN, packetsExpected),
|
||||
"rtpStats", lockedRTPStatsReceiverLogEncoder{r},
|
||||
)
|
||||
return nil
|
||||
}
|
||||
if packetsExpected == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
packetsLost := uint32(now.packetsLost - then.packetsLost)
|
||||
if int32(packetsLost) < 0 {
|
||||
packetsLost = 0
|
||||
}
|
||||
lossRate := float32(packetsLost) / float32(packetsExpected)
|
||||
fracLost := uint8(lossRate * 256.0)
|
||||
if proxyFracLost > fracLost {
|
||||
fracLost = proxyFracLost
|
||||
}
|
||||
|
||||
totalLost := r.packetsLost
|
||||
if totalLost > 0xffffff { // 24-bits max
|
||||
totalLost = 0xffffff
|
||||
}
|
||||
|
||||
lastSR := uint32(0)
|
||||
dlsr := uint32(0)
|
||||
if r.srNewest != nil {
|
||||
lastSR = uint32(r.srNewest.NtpTimestamp >> 16)
|
||||
if r.srNewest.At != 0 {
|
||||
delayUS := time.Since(time.Unix(0, r.srNewest.At)).Microseconds()
|
||||
dlsr = uint32(delayUS * 65536 / 1e6)
|
||||
}
|
||||
}
|
||||
|
||||
return &rtcp.ReceptionReport{
|
||||
SSRC: ssrc,
|
||||
FractionLost: fracLost,
|
||||
TotalLost: uint32(totalLost),
|
||||
LastSequenceNumber: uint32(now.extStartSN),
|
||||
Jitter: uint32(r.jitter),
|
||||
LastSenderReport: lastSR,
|
||||
Delay: dlsr,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *RTPStatsReceiver) DeltaInfo(snapshotID uint32) *RTPDeltaInfo {
|
||||
r.lock.Lock()
|
||||
defer r.lock.Unlock()
|
||||
|
||||
deltaInfo, err, loggingFields := r.deltaInfo(
|
||||
snapshotID,
|
||||
r.sequenceNumber.GetExtendedStart(),
|
||||
r.sequenceNumber.GetExtendedHighest(),
|
||||
)
|
||||
if err != nil {
|
||||
r.logger.Infow(err.Error(), append(loggingFields, "rtpStats", lockedRTPStatsReceiverLogEncoder{r})...)
|
||||
}
|
||||
|
||||
return deltaInfo
|
||||
}
|
||||
|
||||
func (r *RTPStatsReceiver) MarshalLogObject(e zapcore.ObjectEncoder) error {
|
||||
if r == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
r.lock.RLock()
|
||||
defer r.lock.RUnlock()
|
||||
|
||||
return lockedRTPStatsReceiverLogEncoder{r}.MarshalLogObject(e)
|
||||
}
|
||||
|
||||
func (r *RTPStatsReceiver) ToProto() *livekit.RTPStats {
|
||||
r.lock.RLock()
|
||||
defer r.lock.RUnlock()
|
||||
|
||||
extStartSN, extHighestSN := r.sequenceNumber.GetExtendedStart(), r.sequenceNumber.GetExtendedHighest()
|
||||
return r.toProto(
|
||||
getPacketsExpected(extStartSN, extHighestSN),
|
||||
r.getPacketsSeenMinusPadding(extStartSN, extHighestSN),
|
||||
r.packetsLost,
|
||||
r.timestamp.GetExtendedStart(),
|
||||
r.timestamp.GetExtendedHighest(),
|
||||
r.jitter,
|
||||
r.maxJitter,
|
||||
)
|
||||
}
|
||||
|
||||
func (r *RTPStatsReceiver) isInRange(esn uint64, ehsn uint64) bool {
|
||||
diff := int64(ehsn - esn)
|
||||
return diff >= 0 && diff < cHistorySize
|
||||
}
|
||||
|
||||
func (r *RTPStatsReceiver) HighestTimestamp() uint32 {
|
||||
r.lock.RLock()
|
||||
defer r.lock.RUnlock()
|
||||
|
||||
return r.timestamp.GetHighest()
|
||||
}
|
||||
|
||||
// for testing only
|
||||
func (r *RTPStatsReceiver) HighestSequenceNumber() uint16 {
|
||||
r.lock.RLock()
|
||||
defer r.lock.RUnlock()
|
||||
|
||||
return r.sequenceNumber.GetHighest()
|
||||
}
|
||||
|
||||
// for testing only
|
||||
func (r *RTPStatsReceiver) ExtendedHighestSequenceNumber() uint64 {
|
||||
r.lock.RLock()
|
||||
defer r.lock.RUnlock()
|
||||
|
||||
return r.sequenceNumber.GetExtendedHighest()
|
||||
}
|
||||
|
||||
// ----------------------------------
|
||||
|
||||
type lockedRTPStatsReceiverLogEncoder struct {
|
||||
*RTPStatsReceiver
|
||||
}
|
||||
|
||||
func (r lockedRTPStatsReceiverLogEncoder) MarshalLogObject(e zapcore.ObjectEncoder) error {
|
||||
if r.RTPStatsReceiver == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
extStartSN, extHighestSN := r.sequenceNumber.GetExtendedStart(), r.sequenceNumber.GetExtendedHighest()
|
||||
extStartTS, extHighestTS := r.timestamp.GetExtendedStart(), r.timestamp.GetExtendedHighest()
|
||||
if _, err := r.rtpStatsBase.marshalLogObject(
|
||||
e,
|
||||
getPacketsExpected(extStartSN, extHighestSN),
|
||||
r.getPacketsSeenMinusPadding(extStartSN, extHighestSN),
|
||||
extStartTS,
|
||||
extHighestTS,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
e.AddUint64("extStartSN", extStartSN)
|
||||
e.AddUint64("extHighestSN", extHighestSN)
|
||||
e.AddUint64("extStartTS", extStartTS)
|
||||
e.AddUint64("extHighestTS", extHighestTS)
|
||||
|
||||
e.AddObject("propagationDelayEstimator", r.propagationDelayEstimator)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ----------------------------------
|
||||
@@ -0,0 +1,177 @@
|
||||
// Copyright 2023 LiveKit, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package rtpstats
|
||||
|
||||
import (
|
||||
"go.uber.org/zap/zapcore"
|
||||
|
||||
"github.com/livekit/livekit-server/pkg/sfu/utils"
|
||||
"github.com/livekit/protocol/livekit"
|
||||
"github.com/livekit/protocol/utils/mono"
|
||||
)
|
||||
|
||||
type RTPFlowStateLite struct {
|
||||
IsNotHandled bool
|
||||
|
||||
LossStartInclusive uint64
|
||||
LossEndExclusive uint64
|
||||
|
||||
ExtSequenceNumber uint64
|
||||
}
|
||||
|
||||
func (r *RTPFlowStateLite) MarshalLogObject(e zapcore.ObjectEncoder) error {
|
||||
if r == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
e.AddBool("IsNotHandled", r.IsNotHandled)
|
||||
e.AddUint64("LossStartInclusive", r.LossStartInclusive)
|
||||
e.AddUint64("LossEndExclusive", r.LossEndExclusive)
|
||||
e.AddUint64("ExtSequenceNumber", r.ExtSequenceNumber)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
type RTPStatsReceiverLite struct {
|
||||
*rtpStatsBaseLite
|
||||
|
||||
sequenceNumber *utils.WrapAround[uint16, uint64]
|
||||
}
|
||||
|
||||
func NewRTPStatsReceiverLite(params RTPStatsParams) *RTPStatsReceiverLite {
|
||||
return &RTPStatsReceiverLite{
|
||||
rtpStatsBaseLite: newRTPStatsBaseLite(params),
|
||||
sequenceNumber: utils.NewWrapAround[uint16, uint64](utils.WrapAroundParams{IsRestartAllowed: false}),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *RTPStatsReceiverLite) NewSnapshotLiteId() uint32 {
|
||||
r.lock.Lock()
|
||||
defer r.lock.Unlock()
|
||||
|
||||
return r.newSnapshotLiteID(r.sequenceNumber.GetExtendedHighest())
|
||||
}
|
||||
|
||||
func (r *RTPStatsReceiverLite) Update(packetTime int64, packetSize int, sequenceNumber uint16) (flowStateLite RTPFlowStateLite) {
|
||||
r.lock.Lock()
|
||||
defer r.lock.Unlock()
|
||||
|
||||
if r.endTime != 0 {
|
||||
flowStateLite.IsNotHandled = true
|
||||
return
|
||||
}
|
||||
|
||||
var resSN utils.WrapAroundUpdateResult[uint64]
|
||||
if !r.initialized {
|
||||
r.initialized = true
|
||||
|
||||
r.startTime = mono.UnixNano()
|
||||
|
||||
resSN = r.sequenceNumber.Update(sequenceNumber)
|
||||
|
||||
// initialize lite snapshots if any
|
||||
for i := uint32(0); i < r.nextSnapshotLiteID-cFirstSnapshotID; i++ {
|
||||
r.snapshotLites[i] = initSnapshotLite(r.startTime, r.sequenceNumber.GetExtendedStart())
|
||||
}
|
||||
|
||||
r.logger.Debugw(
|
||||
"rtp receiver lite stream start",
|
||||
"rtpStats", lockedRTPStatsReceiverLiteLogEncoder{r},
|
||||
)
|
||||
} else {
|
||||
resSN = r.sequenceNumber.Update(sequenceNumber)
|
||||
if resSN.IsUnhandled {
|
||||
flowStateLite.IsNotHandled = true
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
gapSN := int64(resSN.ExtendedVal - resSN.PreExtendedHighest)
|
||||
if gapSN <= 0 { // duplicate OR out-of-order
|
||||
r.packetsOutOfOrder++ // counting duplicate as out-of-order
|
||||
r.packetsLost--
|
||||
} else { // in-order
|
||||
r.updateGapHistogram(int(gapSN))
|
||||
r.packetsLost += uint64(gapSN - 1)
|
||||
|
||||
flowStateLite.LossStartInclusive = resSN.PreExtendedHighest + 1
|
||||
flowStateLite.LossEndExclusive = resSN.ExtendedVal
|
||||
}
|
||||
flowStateLite.ExtSequenceNumber = resSN.ExtendedVal
|
||||
r.bytes += uint64(packetSize)
|
||||
return
|
||||
}
|
||||
|
||||
func (r *RTPStatsReceiverLite) DeltaInfoLite(snapshotLiteID uint32) *RTPDeltaInfoLite {
|
||||
r.lock.Lock()
|
||||
defer r.lock.Unlock()
|
||||
|
||||
deltaInfoLite, err, loggingFields := r.deltaInfoLite(
|
||||
snapshotLiteID,
|
||||
r.sequenceNumber.GetExtendedStart(),
|
||||
r.sequenceNumber.GetExtendedHighest(),
|
||||
)
|
||||
if err != nil {
|
||||
r.logger.Infow(err.Error(), append(loggingFields, "rtpStats", lockedRTPStatsReceiverLiteLogEncoder{r})...)
|
||||
}
|
||||
|
||||
return deltaInfoLite
|
||||
}
|
||||
|
||||
func (r *RTPStatsReceiverLite) MarshalLogObject(e zapcore.ObjectEncoder) error {
|
||||
if r == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
r.lock.RLock()
|
||||
defer r.lock.RUnlock()
|
||||
|
||||
return lockedRTPStatsReceiverLiteLogEncoder{r}.MarshalLogObject(e)
|
||||
}
|
||||
|
||||
func (r *RTPStatsReceiverLite) ToProto() *livekit.RTPStats {
|
||||
r.lock.RLock()
|
||||
defer r.lock.RUnlock()
|
||||
|
||||
return r.rtpStatsBaseLite.toProto(r.sequenceNumber.GetExtendedStart(), r.sequenceNumber.GetExtendedHighest(), r.packetsLost)
|
||||
}
|
||||
|
||||
// ----------------------------------
|
||||
|
||||
type lockedRTPStatsReceiverLiteLogEncoder struct {
|
||||
*RTPStatsReceiverLite
|
||||
}
|
||||
|
||||
func (r lockedRTPStatsReceiverLiteLogEncoder) MarshalLogObject(e zapcore.ObjectEncoder) error {
|
||||
if r.RTPStatsReceiverLite == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
extStartSN, extHighestSN := r.sequenceNumber.GetExtendedStart(), r.sequenceNumber.GetExtendedHighest()
|
||||
if _, err := r.rtpStatsBaseLite.marshalLogObject(
|
||||
e,
|
||||
getPacketsExpected(extStartSN, extHighestSN),
|
||||
getPacketsExpected(extStartSN, extHighestSN),
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
e.AddUint64("extStartSN", r.sequenceNumber.GetExtendedStart())
|
||||
e.AddUint64("extHighestSN", r.sequenceNumber.GetExtendedHighest())
|
||||
return nil
|
||||
}
|
||||
|
||||
// ----------------------------------
|
||||
@@ -0,0 +1,228 @@
|
||||
// Copyright 2023 LiveKit, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package rtpstats
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/pion/rtp"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/livekit/protocol/logger"
|
||||
)
|
||||
|
||||
func getPacket(sn uint16, ts uint32, payloadSize int) *rtp.Packet {
|
||||
return &rtp.Packet{
|
||||
Header: rtp.Header{
|
||||
SequenceNumber: sn,
|
||||
Timestamp: ts,
|
||||
},
|
||||
Payload: make([]byte, payloadSize),
|
||||
}
|
||||
}
|
||||
|
||||
func Test_RTPStatsReceiver_Update(t *testing.T) {
|
||||
clockRate := uint32(90000)
|
||||
r := NewRTPStatsReceiver(RTPStatsParams{
|
||||
ClockRate: clockRate,
|
||||
Logger: logger.GetLogger(),
|
||||
})
|
||||
|
||||
sequenceNumber := uint16(rand.Float64() * float64(1<<16))
|
||||
timestamp := uint32(rand.Float64() * float64(1<<32))
|
||||
packet := getPacket(sequenceNumber, timestamp, 1000)
|
||||
flowState := r.Update(
|
||||
time.Now().UnixNano(),
|
||||
packet.Header.SequenceNumber,
|
||||
packet.Header.Timestamp,
|
||||
packet.Header.Marker,
|
||||
packet.Header.MarshalSize(),
|
||||
len(packet.Payload),
|
||||
0,
|
||||
)
|
||||
require.True(t, r.initialized)
|
||||
require.Equal(t, sequenceNumber, r.sequenceNumber.GetHighest())
|
||||
require.Equal(t, sequenceNumber, uint16(r.sequenceNumber.GetExtendedHighest()))
|
||||
require.Equal(t, timestamp, r.timestamp.GetHighest())
|
||||
require.Equal(t, timestamp, uint32(r.timestamp.GetExtendedHighest()))
|
||||
|
||||
// in-order, no loss
|
||||
sequenceNumber++
|
||||
timestamp += 3000
|
||||
packet = getPacket(sequenceNumber, timestamp, 1000)
|
||||
flowState = r.Update(
|
||||
time.Now().UnixNano(),
|
||||
packet.Header.SequenceNumber,
|
||||
packet.Header.Timestamp,
|
||||
packet.Header.Marker,
|
||||
packet.Header.MarshalSize(),
|
||||
len(packet.Payload),
|
||||
0,
|
||||
)
|
||||
require.Equal(t, sequenceNumber, r.sequenceNumber.GetHighest())
|
||||
require.Equal(t, sequenceNumber, uint16(r.sequenceNumber.GetExtendedHighest()))
|
||||
require.Equal(t, timestamp, r.timestamp.GetHighest())
|
||||
require.Equal(t, timestamp, uint32(r.timestamp.GetExtendedHighest()))
|
||||
|
||||
// out-of-order, would cause a restart which is disallowed
|
||||
packet = getPacket(sequenceNumber-10, timestamp-30000, 1000)
|
||||
flowState = r.Update(
|
||||
time.Now().UnixNano(),
|
||||
packet.Header.SequenceNumber,
|
||||
packet.Header.Timestamp,
|
||||
packet.Header.Marker,
|
||||
packet.Header.MarshalSize(),
|
||||
len(packet.Payload),
|
||||
0,
|
||||
)
|
||||
require.True(t, flowState.IsNotHandled)
|
||||
require.Equal(t, sequenceNumber, r.sequenceNumber.GetHighest())
|
||||
require.Equal(t, sequenceNumber, uint16(r.sequenceNumber.GetExtendedHighest()))
|
||||
require.Equal(t, timestamp, r.timestamp.GetHighest())
|
||||
require.Equal(t, timestamp, uint32(r.timestamp.GetExtendedHighest()))
|
||||
require.Equal(t, uint64(0), r.packetsOutOfOrder)
|
||||
require.Equal(t, uint64(0), r.packetsDuplicate)
|
||||
|
||||
// duplicate of the above out-of-order packet, but would not be handled as it causes a restart
|
||||
packet = getPacket(sequenceNumber-10, timestamp-30000, 1000)
|
||||
flowState = r.Update(
|
||||
time.Now().UnixNano(),
|
||||
packet.Header.SequenceNumber,
|
||||
packet.Header.Timestamp,
|
||||
packet.Header.Marker,
|
||||
packet.Header.MarshalSize(),
|
||||
len(packet.Payload),
|
||||
0,
|
||||
)
|
||||
require.True(t, flowState.IsNotHandled)
|
||||
require.Equal(t, sequenceNumber, r.sequenceNumber.GetHighest())
|
||||
require.Equal(t, sequenceNumber, uint16(r.sequenceNumber.GetExtendedHighest()))
|
||||
require.Equal(t, timestamp, r.timestamp.GetHighest())
|
||||
require.Equal(t, timestamp, uint32(r.timestamp.GetExtendedHighest()))
|
||||
require.Equal(t, uint64(0), r.packetsOutOfOrder)
|
||||
require.Equal(t, uint64(0), r.packetsDuplicate)
|
||||
|
||||
// loss
|
||||
sequenceNumber += 10
|
||||
timestamp += 30000
|
||||
packet = getPacket(sequenceNumber, timestamp, 1000)
|
||||
flowState = r.Update(
|
||||
time.Now().UnixNano(),
|
||||
packet.Header.SequenceNumber,
|
||||
packet.Header.Timestamp,
|
||||
packet.Header.Marker,
|
||||
packet.Header.MarshalSize(),
|
||||
len(packet.Payload),
|
||||
0,
|
||||
)
|
||||
require.Equal(t, uint64(sequenceNumber-9), flowState.LossStartInclusive)
|
||||
require.Equal(t, uint64(sequenceNumber), flowState.LossEndExclusive)
|
||||
require.Equal(t, uint64(9), r.packetsLost)
|
||||
|
||||
// out-of-order should decrement number of lost packets
|
||||
packet = getPacket(sequenceNumber-6, timestamp-18000, 1000)
|
||||
flowState = r.Update(
|
||||
time.Now().UnixNano(),
|
||||
packet.Header.SequenceNumber,
|
||||
packet.Header.Timestamp,
|
||||
packet.Header.Marker,
|
||||
packet.Header.MarshalSize(),
|
||||
len(packet.Payload),
|
||||
0,
|
||||
)
|
||||
require.Equal(t, sequenceNumber, r.sequenceNumber.GetHighest())
|
||||
require.Equal(t, sequenceNumber, uint16(r.sequenceNumber.GetExtendedHighest()))
|
||||
require.Equal(t, timestamp, r.timestamp.GetHighest())
|
||||
require.Equal(t, timestamp, uint32(r.timestamp.GetExtendedHighest()))
|
||||
require.Equal(t, uint64(1), r.packetsOutOfOrder)
|
||||
require.Equal(t, uint64(0), r.packetsDuplicate)
|
||||
require.Equal(t, uint64(8), r.packetsLost)
|
||||
|
||||
// test sequence number history
|
||||
// with a gap
|
||||
sequenceNumber += 2
|
||||
timestamp += 6000
|
||||
packet = getPacket(sequenceNumber, timestamp, 1000)
|
||||
flowState = r.Update(
|
||||
time.Now().UnixNano(),
|
||||
packet.Header.SequenceNumber,
|
||||
packet.Header.Timestamp,
|
||||
packet.Header.Marker,
|
||||
packet.Header.MarshalSize(),
|
||||
len(packet.Payload),
|
||||
0,
|
||||
)
|
||||
require.Equal(t, uint64(sequenceNumber-1), flowState.LossStartInclusive)
|
||||
require.Equal(t, uint64(sequenceNumber), flowState.LossEndExclusive)
|
||||
require.Equal(t, uint64(9), r.packetsLost)
|
||||
require.False(t, r.history.IsSet(uint64(sequenceNumber)-1))
|
||||
|
||||
// out-of-order
|
||||
sequenceNumber--
|
||||
timestamp -= 3000
|
||||
packet = getPacket(sequenceNumber, timestamp, 999)
|
||||
flowState = r.Update(
|
||||
time.Now().UnixNano(),
|
||||
packet.Header.SequenceNumber,
|
||||
packet.Header.Timestamp,
|
||||
packet.Header.Marker,
|
||||
packet.Header.MarshalSize(),
|
||||
len(packet.Payload),
|
||||
0,
|
||||
)
|
||||
require.Equal(t, uint64(8), r.packetsLost)
|
||||
require.Equal(t, uint64(2), r.packetsOutOfOrder)
|
||||
require.True(t, r.history.IsSet(uint64(sequenceNumber)))
|
||||
|
||||
// padding only
|
||||
sequenceNumber += 2
|
||||
timestamp += 3000
|
||||
packet = getPacket(sequenceNumber, timestamp, 0)
|
||||
flowState = r.Update(
|
||||
time.Now().UnixNano(),
|
||||
packet.Header.SequenceNumber,
|
||||
packet.Header.Timestamp,
|
||||
packet.Header.Marker,
|
||||
packet.Header.MarshalSize(),
|
||||
len(packet.Payload),
|
||||
25,
|
||||
)
|
||||
require.Equal(t, uint64(8), r.packetsLost)
|
||||
require.Equal(t, uint64(2), r.packetsOutOfOrder)
|
||||
require.True(t, r.history.IsSet(uint64(sequenceNumber)))
|
||||
require.True(t, r.history.IsSet(uint64(sequenceNumber)-1))
|
||||
require.True(t, r.history.IsSet(uint64(sequenceNumber)-2))
|
||||
|
||||
// old packet, but simulating increasing sequence number after roll over
|
||||
packet = getPacket(sequenceNumber+400, timestamp-6000, 300)
|
||||
flowState = r.Update(
|
||||
time.Now().UnixNano(),
|
||||
packet.Header.SequenceNumber,
|
||||
packet.Header.Timestamp,
|
||||
packet.Header.Marker,
|
||||
packet.Header.MarshalSize(),
|
||||
len(packet.Payload),
|
||||
0,
|
||||
)
|
||||
require.True(t, flowState.IsNotHandled)
|
||||
require.Equal(t, sequenceNumber, r.sequenceNumber.GetHighest())
|
||||
require.Equal(t, sequenceNumber, uint16(r.sequenceNumber.GetExtendedHighest()))
|
||||
require.Equal(t, timestamp, r.timestamp.GetHighest())
|
||||
require.Equal(t, timestamp, uint32(r.timestamp.GetExtendedHighest()))
|
||||
|
||||
r.Stop()
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,121 @@
|
||||
// Copyright 2023 LiveKit, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package rtpstats
|
||||
|
||||
import (
|
||||
"github.com/livekit/protocol/livekit"
|
||||
"github.com/livekit/protocol/utils/mono"
|
||||
"go.uber.org/zap/zapcore"
|
||||
)
|
||||
|
||||
type RTPStatsSenderLite struct {
|
||||
*rtpStatsBaseLite
|
||||
|
||||
extStartSN uint64
|
||||
extHighestSN uint64
|
||||
}
|
||||
|
||||
func NewRTPStatsSenderLite(params RTPStatsParams) *RTPStatsSenderLite {
|
||||
return &RTPStatsSenderLite{
|
||||
rtpStatsBaseLite: newRTPStatsBaseLite(params),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *RTPStatsSenderLite) Update(packetTime int64, packetSize int, extSequenceNumber uint64) {
|
||||
r.lock.Lock()
|
||||
defer r.lock.Unlock()
|
||||
|
||||
if r.endTime != 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if !r.initialized {
|
||||
r.initialized = true
|
||||
|
||||
r.startTime = mono.UnixNano()
|
||||
|
||||
r.extStartSN = extSequenceNumber
|
||||
r.extHighestSN = extSequenceNumber - 1
|
||||
|
||||
r.logger.Debugw(
|
||||
"rtp sender lite stream start",
|
||||
"rtpStats", lockedRTPStatsSenderLiteLogEncoder{r},
|
||||
)
|
||||
}
|
||||
|
||||
gapSN := int64(extSequenceNumber - r.extHighestSN)
|
||||
if gapSN <= 0 { // duplicate OR out-of-order
|
||||
r.packetsOutOfOrder++ // counting duplicate as out-of-order
|
||||
r.packetsLost--
|
||||
} else { // in-order
|
||||
r.updateGapHistogram(int(gapSN))
|
||||
r.packetsLost += uint64(gapSN - 1)
|
||||
|
||||
r.extHighestSN = extSequenceNumber
|
||||
}
|
||||
|
||||
r.bytes += uint64(packetSize)
|
||||
}
|
||||
|
||||
func (r *RTPStatsSenderLite) MarshalLogObject(e zapcore.ObjectEncoder) error {
|
||||
if r == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
r.lock.RLock()
|
||||
defer r.lock.RUnlock()
|
||||
|
||||
return lockedRTPStatsSenderLiteLogEncoder{r}.MarshalLogObject(e)
|
||||
}
|
||||
|
||||
func (r *RTPStatsSenderLite) ToProto() *livekit.RTPStats {
|
||||
r.lock.RLock()
|
||||
defer r.lock.RUnlock()
|
||||
|
||||
return r.rtpStatsBaseLite.toProto(r.extStartSN, r.extHighestSN, r.packetsLost)
|
||||
}
|
||||
|
||||
func (r *RTPStatsSenderLite) ExtHighestSequenceNumber() uint64 {
|
||||
r.lock.RLock()
|
||||
defer r.lock.RUnlock()
|
||||
|
||||
return r.extHighestSN
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
type lockedRTPStatsSenderLiteLogEncoder struct {
|
||||
*RTPStatsSenderLite
|
||||
}
|
||||
|
||||
func (r lockedRTPStatsSenderLiteLogEncoder) MarshalLogObject(e zapcore.ObjectEncoder) error {
|
||||
if r.RTPStatsSenderLite == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if _, err := r.rtpStatsBaseLite.marshalLogObject(
|
||||
e,
|
||||
getPacketsExpected(r.extStartSN, r.extHighestSN),
|
||||
getPacketsExpected(r.extStartSN, r.extHighestSN),
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
e.AddUint64("extStartSN", r.extStartSN)
|
||||
e.AddUint64("extHighestSN", r.extHighestSN)
|
||||
return nil
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
Reference in New Issue
Block a user