Squash integration of the reviewed Go core, frontend audit fixes, release tooling, and deployment hygiene. CI passed all Server/Client jobs.
161 lines
4.3 KiB
Go
161 lines
4.3 KiB
Go
package mediaedge
|
|
|
|
import (
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/pion/interceptor"
|
|
"github.com/pion/interceptor/pkg/cc"
|
|
"github.com/pion/interceptor/pkg/gcc"
|
|
"github.com/pion/webrtc/v4"
|
|
)
|
|
|
|
const defaultNativeSourceInitialBitrate = 3_000_000
|
|
|
|
type targetBitrateEstimator interface {
|
|
GetTargetBitrate() int
|
|
OnTargetBitrateChange(func(int))
|
|
}
|
|
|
|
type bandwidthObserver struct {
|
|
estimator targetBitrateEstimator
|
|
|
|
mu sync.RWMutex
|
|
observed bool
|
|
}
|
|
|
|
func newBandwidthObserver(estimator targetBitrateEstimator) *bandwidthObserver {
|
|
observer := &bandwidthObserver{estimator: estimator}
|
|
estimator.OnTargetBitrateChange(func(int) {
|
|
observer.mu.Lock()
|
|
observer.observed = true
|
|
observer.mu.Unlock()
|
|
})
|
|
return observer
|
|
}
|
|
|
|
type QualitySample struct {
|
|
SampleTimestampMs int64
|
|
SampleWindowMs int64
|
|
RTPStatsID string
|
|
TrackIdentifier string
|
|
State string
|
|
Reason *string
|
|
IntervalFramesEncoded uint64
|
|
FramesPerSecond float64
|
|
BitrateKbps float64
|
|
AvailableOutgoingKbps float64
|
|
Width uint32
|
|
Height uint32
|
|
}
|
|
|
|
type qualityBaseline struct {
|
|
at time.Time
|
|
frames uint64
|
|
videoBytes uint64
|
|
audioBytes uint64
|
|
}
|
|
|
|
func (edge *Edge) QualitySample(now time.Time) (QualitySample, bool) {
|
|
video := edge.source.snapshot()
|
|
audioBytes := edge.audioSource.snapshotBytes()
|
|
edge.qualityMu.Lock()
|
|
previous := edge.qualityBaseline
|
|
edge.qualityBaseline = qualityBaseline{
|
|
at: now, frames: video.frames, videoBytes: video.bytes, audioBytes: audioBytes,
|
|
}
|
|
edge.qualityMu.Unlock()
|
|
if previous.at.IsZero() {
|
|
return QualitySample{}, false
|
|
}
|
|
window := now.Sub(previous.at)
|
|
if window < time.Second || window > 5*time.Second ||
|
|
video.frames < previous.frames || video.bytes < previous.videoBytes ||
|
|
audioBytes < previous.audioBytes {
|
|
return QualitySample{}, false
|
|
}
|
|
frames := video.frames - previous.frames
|
|
bytes := video.bytes - previous.videoBytes + audioBytes - previous.audioBytes
|
|
seconds := window.Seconds()
|
|
bitrate := float64(bytes*8) / seconds
|
|
sample := QualitySample{
|
|
SampleTimestampMs: now.UnixMilli(),
|
|
SampleWindowMs: window.Milliseconds(),
|
|
RTPStatsID: edge.connection.ID(),
|
|
TrackIdentifier: edge.source.track.ID(),
|
|
State: "unknown",
|
|
IntervalFramesEncoded: frames,
|
|
FramesPerSecond: float64(frames) / seconds,
|
|
BitrateKbps: bitrate / 1000,
|
|
Width: video.width,
|
|
Height: video.height,
|
|
}
|
|
target, observed := edge.bandwidth.targetBitrate()
|
|
if edge.State() != webrtc.PeerConnectionStateConnected || !observed ||
|
|
frames == 0 || bytes == 0 {
|
|
return sample, true
|
|
}
|
|
sample.AvailableOutgoingKbps = float64(target) / 1000
|
|
reason := "none"
|
|
sample.State = "healthy"
|
|
if float64(target) < bitrate {
|
|
reason = "bandwidth"
|
|
sample.State = "degraded"
|
|
}
|
|
sample.Reason = &reason
|
|
return sample, true
|
|
}
|
|
|
|
func (observer *bandwidthObserver) targetBitrate() (int, bool) {
|
|
observer.mu.RLock()
|
|
observed := observer.observed
|
|
observer.mu.RUnlock()
|
|
if !observed {
|
|
return 0, false
|
|
}
|
|
return observer.estimator.GetTargetBitrate(), true
|
|
}
|
|
|
|
type bandwidthObservers struct {
|
|
mu sync.Mutex
|
|
pending map[string]*bandwidthObserver
|
|
}
|
|
|
|
func configureBandwidthObservers(
|
|
media *webrtc.MediaEngine,
|
|
registry *interceptor.Registry,
|
|
initialBitrate int,
|
|
) (*bandwidthObservers, error) {
|
|
if initialBitrate <= 0 {
|
|
initialBitrate = defaultNativeSourceInitialBitrate
|
|
}
|
|
observers := &bandwidthObservers{pending: make(map[string]*bandwidthObserver)}
|
|
factory, err := cc.NewInterceptor(func() (cc.BandwidthEstimator, error) {
|
|
return gcc.NewSendSideBWE(
|
|
gcc.SendSideBWEInitialBitrate(initialBitrate),
|
|
gcc.SendSideBWEPacer(gcc.NewNoOpPacer()),
|
|
)
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
factory.OnNewPeerConnection(func(id string, estimator cc.BandwidthEstimator) {
|
|
observers.mu.Lock()
|
|
observers.pending[id] = newBandwidthObserver(estimator)
|
|
observers.mu.Unlock()
|
|
})
|
|
registry.Add(factory)
|
|
if err = webrtc.ConfigureTWCCHeaderExtensionSender(media, registry); err != nil {
|
|
return nil, err
|
|
}
|
|
return observers, nil
|
|
}
|
|
|
|
func (observers *bandwidthObservers) take(id string) *bandwidthObserver {
|
|
observers.mu.Lock()
|
|
defer observers.mu.Unlock()
|
|
observer := observers.pending[id]
|
|
delete(observers.pending, id)
|
|
return observer
|
|
}
|