Files
webrtc/stats_go.go
Sean DuBois 71b8a13dc9 Don't drop packets when probing Simulcast
Before any packets that we read during the probe would get lost

Co-authored-by: cptpcrd <31829097+cptpcrd@users.noreply.github.com>
2025-11-29 07:42:50 -05:00

244 lines
5.8 KiB
Go

// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
//go:build !js
// +build !js
package webrtc
import (
"context"
"sync"
"time"
)
// GetConnectionStats is a helper method to return the associated stats for a given PeerConnection.
func (r StatsReport) GetConnectionStats(conn *PeerConnection) (PeerConnectionStats, bool) {
statsID := conn.ID()
stats, ok := r[statsID]
if !ok {
return PeerConnectionStats{}, false
}
pcStats, ok := stats.(PeerConnectionStats)
if !ok {
return PeerConnectionStats{}, false
}
return pcStats, true
}
// GetDataChannelStats is a helper method to return the associated stats for a given DataChannel.
func (r StatsReport) GetDataChannelStats(dc *DataChannel) (DataChannelStats, bool) {
statsID := dc.getStatsID()
stats, ok := r[statsID]
if !ok {
return DataChannelStats{}, false
}
dcStats, ok := stats.(DataChannelStats)
if !ok {
return DataChannelStats{}, false
}
return dcStats, true
}
// GetICECandidateStats is a helper method to return the associated stats for a given ICECandidate.
func (r StatsReport) GetICECandidateStats(c *ICECandidate) (ICECandidateStats, bool) {
statsID := c.statsID
stats, ok := r[statsID]
if !ok {
return ICECandidateStats{}, false
}
candidateStats, ok := stats.(ICECandidateStats)
if !ok {
return ICECandidateStats{}, false
}
return candidateStats, true
}
// GetICECandidatePairStats is a helper method to return the associated stats for a given ICECandidatePair.
func (r StatsReport) GetICECandidatePairStats(c *ICECandidatePair) (ICECandidatePairStats, bool) {
statsID := c.statsID
stats, ok := r[statsID]
if !ok {
return ICECandidatePairStats{}, false
}
candidateStats, ok := stats.(ICECandidatePairStats)
if !ok {
return ICECandidatePairStats{}, false
}
return candidateStats, true
}
// GetCertificateStats is a helper method to return the associated stats for a given Certificate.
func (r StatsReport) GetCertificateStats(c *Certificate) (CertificateStats, bool) {
statsID := c.statsID
stats, ok := r[statsID]
if !ok {
return CertificateStats{}, false
}
certificateStats, ok := stats.(CertificateStats)
if !ok {
return CertificateStats{}, false
}
return certificateStats, true
}
// GetCodecStats is a helper method to return the associated stats for a given Codec.
func (r StatsReport) GetCodecStats(c *RTPCodecParameters) (CodecStats, bool) {
statsID := c.statsID
stats, ok := r[statsID]
if !ok {
return CodecStats{}, false
}
codecStats, ok := stats.(CodecStats)
if !ok {
return CodecStats{}, false
}
return codecStats, true
}
// AudioPlayoutStatsProvider is an interface for getting audio playout metrics.
type AudioPlayoutStatsProvider interface {
// AddTrack registers a track to report playout stats to this provider.
AddTrack(track *TrackRemote) error
// RemoveTrack unregisters a track from this provider.
RemoveTrack(track *TrackRemote)
// Snapshot returns the accumulated stats at the given time.
Snapshot(now time.Time) (AudioPlayoutStats, bool)
}
type trackContext struct {
cancel context.CancelFunc
}
// defaultAudioPlayoutStatsProvider accumulates audio playout stats on behalf of the application.
type defaultAudioPlayoutStatsProvider struct {
mu sync.Mutex
stats AudioPlayoutStats
lastSynthesized bool
tracks map[*TrackRemote]*trackContext
}
// NewAudioPlayoutStatsProvider constructs a default provider with the supplied stats ID.
func NewAudioPlayoutStatsProvider(id string) *defaultAudioPlayoutStatsProvider {
return &defaultAudioPlayoutStatsProvider{
stats: AudioPlayoutStats{
ID: id,
Type: StatsTypeMediaPlayout,
Kind: string(MediaKindAudio),
},
tracks: make(map[*TrackRemote]*trackContext),
}
}
// Accumulate applies a new batch of played-out samples to the running totals.
func (p *defaultAudioPlayoutStatsProvider) Accumulate(
samples int, sampleRate uint32, deviceDelay time.Duration, synthesized bool,
) {
if samples <= 0 || sampleRate == 0 {
return
}
delaySeconds := deviceDelay.Seconds()
if delaySeconds < 0 {
delaySeconds = 0
}
duration := float64(samples) / float64(sampleRate)
p.mu.Lock()
defer p.mu.Unlock()
p.stats.TotalSamplesCount += uint64(samples)
p.stats.TotalSamplesDuration += duration
p.stats.TotalPlayoutDelay += delaySeconds * float64(samples)
if synthesized {
p.stats.SynthesizedSamplesDuration += duration
if !p.lastSynthesized {
p.stats.SynthesizedSamplesEvents++
}
}
p.lastSynthesized = synthesized
}
// Snapshot returns the accumulated stats at the given time.
func (p *defaultAudioPlayoutStatsProvider) Snapshot(now time.Time) (AudioPlayoutStats, bool) {
p.mu.Lock()
defer p.mu.Unlock()
if p.stats.TotalSamplesCount == 0 {
return AudioPlayoutStats{}, false
}
stats := p.stats
stats.Timestamp = statsTimestampFrom(now)
return stats, true
}
// AddTrack registers a track to report playout stats to this provider.
func (p *defaultAudioPlayoutStatsProvider) AddTrack(track *TrackRemote) error {
p.mu.Lock()
defer p.mu.Unlock()
if _, exists := p.tracks[track]; exists {
return nil
}
track.addProvider(p)
ctx, cancel := context.WithCancel(context.Background())
p.tracks[track] = &trackContext{cancel: cancel}
go func() {
receiver := track.receiver
if receiver == nil {
cancel()
return
}
select {
case <-receiver.closedChan:
p.removeTrackInternal(track)
case <-ctx.Done():
return
}
}()
return nil
}
// RemoveTrack unregisters a track from this provider.
func (p *defaultAudioPlayoutStatsProvider) RemoveTrack(track *TrackRemote) {
p.removeTrackInternal(track)
}
func (p *defaultAudioPlayoutStatsProvider) removeTrackInternal(track *TrackRemote) {
p.mu.Lock()
defer p.mu.Unlock()
if tc, exists := p.tracks[track]; exists {
tc.cancel()
delete(p.tracks, track)
}
track.removeProvider(p)
}