Merge remote-tracking branch 'origin/master' into raja_fr

This commit is contained in:
boks1971
2023-08-02 14:05:48 +05:30
37 changed files with 666 additions and 379 deletions
+5
View File
@@ -152,6 +152,11 @@ keys:
# enable_remote_unmute: true
# # limit size of room and participant's metadata, 0 for no limit
# max_metadata_size: 0
# # control playout delay in ms of video track (and associated audio track)
# playout_delay:
# enabled: true
# min: 100
# max: 300
# Webhooks
# when configured, LiveKit notifies your URL handler with room events
+1 -1
View File
@@ -45,7 +45,7 @@ require (
github.com/urfave/cli/v2 v2.25.7
github.com/urfave/negroni/v3 v3.0.0
go.uber.org/atomic v1.11.0
golang.org/x/exp v0.0.0-20230728194245-b0cb94b80691
golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b
golang.org/x/sync v0.3.0
google.golang.org/protobuf v1.31.0
gopkg.in/yaml.v3 v3.0.1
+2 -2
View File
@@ -289,8 +289,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM=
golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
golang.org/x/exp v0.0.0-20230728194245-b0cb94b80691 h1:/yRP+0AN7mf5DkD3BAI6TOFnd51gEoDEb8o35jIFtgw=
golang.org/x/exp v0.0.0-20230728194245-b0cb94b80691/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b h1:r+vk0EmXNmekl0S0BascoeeoHk/L7wmaW2QF90K+kYI=
golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+13 -6
View File
@@ -197,6 +197,12 @@ type StreamTrackersConfig struct {
Screenshare StreamTrackerConfig `yaml:"screenshare,omitempty"`
}
type PlayoutDelayConfig struct {
Enabled bool `yaml:"enabled,omitempty"`
Min int `yaml:"min,omitempty"`
Max int `yaml:"max,omitempty"`
}
type VideoConfig struct {
DynacastPauseDelay time.Duration `yaml:"dynacast_pause_delay,omitempty"`
StreamTracker StreamTrackersConfig `yaml:"stream_tracker,omitempty"`
@@ -204,12 +210,13 @@ type VideoConfig struct {
type RoomConfig struct {
// enable rooms to be automatically created
AutoCreate bool `yaml:"auto_create,omitempty"`
EnabledCodecs []CodecSpec `yaml:"enabled_codecs,omitempty"`
MaxParticipants uint32 `yaml:"max_participants,omitempty"`
EmptyTimeout uint32 `yaml:"empty_timeout,omitempty"`
EnableRemoteUnmute bool `yaml:"enable_remote_unmute,omitempty"`
MaxMetadataSize uint32 `yaml:"max_metadata_size,omitempty"`
AutoCreate bool `yaml:"auto_create,omitempty"`
EnabledCodecs []CodecSpec `yaml:"enabled_codecs,omitempty"`
MaxParticipants uint32 `yaml:"max_participants,omitempty"`
EmptyTimeout uint32 `yaml:"empty_timeout,omitempty"`
EnableRemoteUnmute bool `yaml:"enable_remote_unmute,omitempty"`
MaxMetadataSize uint32 `yaml:"max_metadata_size,omitempty"`
PlayoutDelay PlayoutDelayConfig `yaml:"playout_delay,omitempty"`
}
type CodecSpec struct {
+2 -2
View File
@@ -86,7 +86,7 @@ func NewWebRTCConfig(conf *config.Config) (*WebRTCConfig, error) {
sdp.SDESRTPStreamIDURI,
sdp.TransportCCURI,
frameMarking,
dd.ExtensionUrl,
dd.ExtensionURI,
},
},
RTCPFeedback: RTCPFeedbackConfig{
@@ -106,7 +106,7 @@ func NewWebRTCConfig(conf *config.Config) (*WebRTCConfig, error) {
subscriberConfig := DirectionConfig{
StrictACKs: conf.RTC.StrictACKs,
RTPHeaderExtension: RTPHeaderExtensionConfig{
Video: []string{dd.ExtensionUrl},
Video: []string{dd.ExtensionURI},
},
RTCPFeedback: RTCPFeedbackConfig{
Video: []webrtc.RTCPFeedback{
+1 -1
View File
@@ -238,7 +238,7 @@ func (d *DynacastManager) update(force bool) {
// clear debounce on send
d.maxSubscribedQualityDebounce(func() {})
d.params.Logger.Infow("committing quality change",
d.params.Logger.Debugw("committing quality change",
"force", force,
"committedMaxSubscribedQuality", d.committedMaxSubscribedQuality,
"maxSubscribedQuality", d.maxSubscribedQuality,
+8 -1
View File
@@ -226,7 +226,7 @@ func (t *MediaTrackReceiver) SetPotentialCodecs(codecs []webrtc.RTPCodecParamete
// that is munged in svc codec.
headersWithoutDD := make([]webrtc.RTPHeaderExtensionParameter, 0, len(headers))
for _, h := range headers {
if h.URI != dependencydescriptor.ExtensionUrl {
if h.URI != dependencydescriptor.ExtensionURI {
headersWithoutDD = append(headersWithoutDD, h)
}
}
@@ -401,6 +401,13 @@ func (t *MediaTrackReceiver) Source() livekit.TrackSource {
return t.trackInfo.Source
}
func (t *MediaTrackReceiver) Stream() string {
t.lock.RLock()
defer t.lock.RUnlock()
return t.trackInfo.Stream
}
func (t *MediaTrackReceiver) PublisherID() livekit.ParticipantID {
return t.params.ParticipantID
}
+21 -13
View File
@@ -112,20 +112,29 @@ func (t *MediaTrackSubscriptions) AddSubscriber(sub types.LocalParticipant, wr *
for _, c := range codecs {
c.RTCPFeedback = rtcpFeedback
}
streamID := wr.StreamID()
if sub.SupportSyncStreamID() && t.params.MediaTrack.Stream() != "" {
streamID = PackSyncStreamID(t.params.MediaTrack.PublisherID(), t.params.MediaTrack.Stream())
}
var trailer []byte
if t.params.MediaTrack.IsEncrypted() {
trailer = sub.GetTrailer()
}
downTrack, err := sfu.NewDownTrack(
codecs,
wr,
sub.GetBufferFactory(),
subscriberID,
t.params.ReceiverConfig.PacketBufferSize,
sub.GetPacer(),
trailer,
LoggerWithTrack(sub.GetLogger(), trackID, t.params.IsRelayed),
)
downTrack, err := sfu.NewDownTrack(sfu.DowntrackParams{
Codecs: codecs,
Receiver: wr,
BufferFactory: sub.GetBufferFactory(),
SubID: subscriberID,
StreamID: streamID,
MaxTrack: t.params.ReceiverConfig.PacketBufferSize,
PlayoutDelayLimit: sub.GetPlayoutDelayConfig(),
Pacer: sub.GetPacer(),
Trailer: trailer,
Logger: LoggerWithTrack(sub.GetLogger(), trackID, t.params.IsRelayed),
})
if err != nil {
return nil, err
}
@@ -200,6 +209,8 @@ func (t *MediaTrackSubscriptions) AddSubscriber(sub types.LocalParticipant, wr *
reusingTransceiver.Store(true)
rtpSender := existingTransceiver.Sender()
if rtpSender != nil {
// replaced track will bind immediately without negotiation, SetTransceiver first before bind
downTrack.SetTransceiver(existingTransceiver)
err := rtpSender.ReplaceTrack(downTrack)
if err == nil {
sender = rtpSender
@@ -260,9 +271,6 @@ func (t *MediaTrackSubscriptions) AddSubscriber(sub types.LocalParticipant, wr *
subTrack.SetNeedsNegotiation(!replacedTrack)
subTrack.SetRTPSender(sender)
sendParameters := sender.GetParameters()
downTrack.SetRTPHeaderExtensions(sendParameters.HeaderExtensions)
downTrack.SetTransceiver(transceiver)
downTrack.OnCloseHandler(func(willBeResumed bool) {
+14
View File
@@ -108,6 +108,7 @@ type ParticipantParams struct {
SubscriberAllowPause bool
SubscriptionLimitAudio int32
SubscriptionLimitVideo int32
PlayoutDelay *livekit.PlayoutDelay
}
type ParticipantImpl struct {
@@ -1104,6 +1105,7 @@ func (p *ParticipantImpl) setupTransportManager() error {
TCPFallbackRTTThreshold: p.params.TCPFallbackRTTThreshold,
AllowUDPUnstableFallback: p.params.AllowUDPUnstableFallback,
TURNSEnabled: p.params.TURNSEnabled,
AllowPlayoutDelay: p.params.PlayoutDelay.GetEnabled() && p.SupportSyncStreamID(),
Logger: p.params.Logger,
})
if err != nil {
@@ -1584,6 +1586,10 @@ func (p *ParticipantImpl) addPendingTrackLocked(req *livekit.AddTrackRequest) *l
DisableRed: req.DisableRed,
Stereo: req.Stereo,
Encryption: req.Encryption,
Stream: req.Stream,
}
if ti.Stream == "" {
ti.Stream = StreamFromTrackSource(ti.Source)
}
p.setStableTrackID(req.Cid, ti)
for _, codec := range req.SimulcastCodecs {
@@ -2214,6 +2220,14 @@ func (p *ParticipantImpl) UpdateMediaLoss(nodeID livekit.NodeID, trackID livekit
return nil
}
func (p *ParticipantImpl) GetPlayoutDelayConfig() *livekit.PlayoutDelay {
return p.params.PlayoutDelay
}
func (p *ParticipantImpl) SupportSyncStreamID() bool {
return p.ProtocolVersion().SupportSyncStreamID() && !p.params.ClientInfo.isFirefox()
}
func codecsFromMediaDescription(m *sdp.MediaDescription) (out []sdp.Codec, err error) {
s := &sdp.SessionDescription{
MediaDescriptions: []*sdp.MediaDescription{m},
+1 -1
View File
@@ -155,7 +155,7 @@ func (p *ParticipantImpl) setCodecPreferencesVideoForPublisher(offer webrtc.Sess
// remove dd extension if av1/vp9 not preferred
if !strings.Contains(strings.ToLower(mime), "av1") && !strings.Contains(strings.ToLower(mime), "vp9") {
for i, attr := range unmatchVideo.Attributes {
if strings.Contains(attr.Value, dd.ExtensionUrl) {
if strings.Contains(attr.Value, dd.ExtensionURI) {
unmatchVideo.Attributes[i] = unmatchVideo.Attributes[len(unmatchVideo.Attributes)-1]
unmatchVideo.Attributes = unmatchVideo.Attributes[:len(unmatchVideo.Attributes)-1]
break
+2 -1
View File
@@ -30,6 +30,7 @@ import (
"github.com/livekit/protocol/livekit"
"github.com/livekit/protocol/logger"
"github.com/livekit/protocol/utils"
"github.com/livekit/psrpc"
"github.com/livekit/livekit-server/pkg/config"
"github.com/livekit/livekit-server/pkg/routing"
@@ -1310,7 +1311,7 @@ func BroadcastDataPacketForRoom(r types.Room, source types.LocalParticipant, dp
utils.ParallelExec(destParticipants, dataForwardLoadBalanceThreshold, 1, func(op types.LocalParticipant) {
err := op.SendDataPacket(dp, dpData)
if err != nil && !errors.Is(err, io.ErrClosedPipe) {
if err != nil && !errors.Is(err, io.ErrClosedPipe) && !errors.Is(err, psrpc.Canceled) {
op.GetLogger().Infow("send data packet error", "error", err)
}
})
+8 -8
View File
@@ -193,7 +193,7 @@ func (t *SubscribedTrack) UpdateSubscriberSettings(settings *livekit.UpdateTrack
t.settings.Store(settings)
if prevDisabled != settings.Disabled {
t.logger.Infow("updated subscribed track enabled", "enabled", !settings.Disabled)
t.logger.Debugw("updated subscribed track enabled", "enabled", !settings.Disabled)
}
// avoid frequent changes to mute & video layers, unless it became visible
@@ -215,14 +215,14 @@ func (t *SubscribedTrack) UpdateVideoLayer() {
return
}
t.logger.Debugw("updating video layer",
"settings", settings,
)
t.logger.Debugw("updating video layer", "settings", settings)
spatial := t.spatialLayerFromSettings(settings)
t.DownTrack().SetMaxSpatialLayer(spatial)
if settings.Fps > 0 {
t.DownTrack().SetMaxTemporalLayer(t.MediaTrack().GetTemporalLayerForSpatialFps(spatial, settings.Fps, t.DownTrack().Codec().MimeType))
if settings.Width > 0 || settings.Fps > 0 {
spatial := t.spatialLayerFromSettings(settings)
t.DownTrack().SetMaxSpatialLayer(spatial)
if settings.Fps > 0 {
t.DownTrack().SetMaxTemporalLayer(t.MediaTrack().GetTemporalLayerForSpatialFps(spatial, settings.Fps, t.DownTrack().Codec().MimeType))
}
}
}
+2 -2
View File
@@ -148,7 +148,7 @@ func (m *SubscriptionManager) SubscribeToTrack(trackID livekit.TrackID) {
sub, desireChanged = m.setDesired(trackID, true)
}
if desireChanged {
sub.logger.Infow("subscribing to track")
sub.logger.Debugw("subscribing to track")
}
// always reconcile, since SubscribeToTrack could be called when the track is ready
@@ -161,7 +161,7 @@ func (m *SubscriptionManager) UnsubscribeFromTrack(trackID livekit.TrackID) {
return
}
sub.logger.Infow("unsubscribing from track")
sub.logger.Debugw("unsubscribing from track")
m.queueReconcile(trackID)
}
+9 -1
View File
@@ -43,6 +43,7 @@ import (
"github.com/livekit/livekit-server/pkg/config"
"github.com/livekit/livekit-server/pkg/rtc/types"
"github.com/livekit/livekit-server/pkg/sfu/pacer"
"github.com/livekit/livekit-server/pkg/sfu/rtpextension"
"github.com/livekit/livekit-server/pkg/sfu/streamallocator"
"github.com/livekit/livekit-server/pkg/telemetry"
"github.com/livekit/livekit-server/pkg/telemetry/prometheus"
@@ -251,10 +252,17 @@ type TransportParams struct {
ClientInfo ClientInfo
IsOfferer bool
IsSendSide bool
AllowPlayoutDelay bool
}
func newPeerConnection(params TransportParams, onBandwidthEstimator func(estimator cc.BandwidthEstimator)) (*webrtc.PeerConnection, *webrtc.MediaEngine, error) {
me, err := createMediaEngine(params.EnabledCodecs, params.DirectionConfig)
directionConfig := params.DirectionConfig
if params.AllowPlayoutDelay {
directionConfig.RTPHeaderExtension.Video = append(directionConfig.RTPHeaderExtension.Video, rtpextension.PlayoutDelayURI)
}
me, err := createMediaEngine(params.EnabledCodecs, directionConfig)
if err != nil {
return nil, nil, err
}
+2
View File
@@ -65,6 +65,7 @@ type TransportManagerParams struct {
TCPFallbackRTTThreshold int
AllowUDPUnstableFallback bool
TURNSEnabled bool
AllowPlayoutDelay bool
Logger logger.Logger
}
@@ -184,6 +185,7 @@ func NewTransportManager(params TransportManagerParams) (*TransportManager, erro
ClientInfo: params.ClientInfo,
IsOfferer: true,
IsSendSide: true,
AllowPlayoutDelay: params.AllowPlayoutDelay,
})
if err != nil {
return nil, err
+3
View File
@@ -295,6 +295,7 @@ type LocalParticipant interface {
GetLogger() logger.Logger
GetAdaptiveStream() bool
ProtocolVersion() ProtocolVersion
SupportSyncStreamID() bool
ConnectedAt() time.Time
IsClosed() bool
IsReady() bool
@@ -305,6 +306,7 @@ type LocalParticipant interface {
GetClientConfiguration() *livekit.ClientConfiguration
GetICEConnectionType() ICEConnectionType
GetBufferFactory() *buffer.Factory
GetPlayoutDelayConfig() *livekit.PlayoutDelay
SetResponseSink(sink routing.MessageSink)
CloseSignalConnection(reason SignallingCloseReason)
@@ -429,6 +431,7 @@ type MediaTrack interface {
Kind() livekit.TrackType
Name() string
Source() livekit.TrackSource
Stream() string
ToProto() *livekit.TrackInfo
+5 -1
View File
@@ -16,7 +16,7 @@ package types
type ProtocolVersion int
const CurrentProtocol = 9
const CurrentProtocol = 10
func (v ProtocolVersion) SupportsPackedStreamId() bool {
return v > 0
@@ -71,3 +71,7 @@ func (v ProtocolVersion) SupportFastStart() bool {
func (v ProtocolVersion) SupportHandlesDisconnectedUpdate() bool {
return v > 8
}
func (v ProtocolVersion) SupportSyncStreamID() bool {
return v > 9
}
@@ -302,6 +302,16 @@ type FakeLocalMediaTrack struct {
sourceReturnsOnCall map[int]struct {
result1 livekit.TrackSource
}
StreamStub func() string
streamMutex sync.RWMutex
streamArgsForCall []struct {
}
streamReturns struct {
result1 string
}
streamReturnsOnCall map[int]struct {
result1 string
}
ToProtoStub func() *livekit.TrackInfo
toProtoMutex sync.RWMutex
toProtoArgsForCall []struct {
@@ -1893,6 +1903,59 @@ func (fake *FakeLocalMediaTrack) SourceReturnsOnCall(i int, result1 livekit.Trac
}{result1}
}
func (fake *FakeLocalMediaTrack) Stream() string {
fake.streamMutex.Lock()
ret, specificReturn := fake.streamReturnsOnCall[len(fake.streamArgsForCall)]
fake.streamArgsForCall = append(fake.streamArgsForCall, struct {
}{})
stub := fake.StreamStub
fakeReturns := fake.streamReturns
fake.recordInvocation("Stream", []interface{}{})
fake.streamMutex.Unlock()
if stub != nil {
return stub()
}
if specificReturn {
return ret.result1
}
return fakeReturns.result1
}
func (fake *FakeLocalMediaTrack) StreamCallCount() int {
fake.streamMutex.RLock()
defer fake.streamMutex.RUnlock()
return len(fake.streamArgsForCall)
}
func (fake *FakeLocalMediaTrack) StreamCalls(stub func() string) {
fake.streamMutex.Lock()
defer fake.streamMutex.Unlock()
fake.StreamStub = stub
}
func (fake *FakeLocalMediaTrack) StreamReturns(result1 string) {
fake.streamMutex.Lock()
defer fake.streamMutex.Unlock()
fake.StreamStub = nil
fake.streamReturns = struct {
result1 string
}{result1}
}
func (fake *FakeLocalMediaTrack) StreamReturnsOnCall(i int, result1 string) {
fake.streamMutex.Lock()
defer fake.streamMutex.Unlock()
fake.StreamStub = nil
if fake.streamReturnsOnCall == nil {
fake.streamReturnsOnCall = make(map[int]struct {
result1 string
})
}
fake.streamReturnsOnCall[i] = struct {
result1 string
}{result1}
}
func (fake *FakeLocalMediaTrack) ToProto() *livekit.TrackInfo {
fake.toProtoMutex.Lock()
ret, specificReturn := fake.toProtoReturnsOnCall[len(fake.toProtoArgsForCall)]
@@ -2050,6 +2113,8 @@ func (fake *FakeLocalMediaTrack) Invocations() map[string][][]interface{} {
defer fake.signalCidMutex.RUnlock()
fake.sourceMutex.RLock()
defer fake.sourceMutex.RUnlock()
fake.streamMutex.RLock()
defer fake.streamMutex.RUnlock()
fake.toProtoMutex.RLock()
defer fake.toProtoMutex.RUnlock()
fake.updateVideoLayersMutex.RLock()
@@ -263,6 +263,16 @@ type FakeLocalParticipant struct {
getPacerReturnsOnCall map[int]struct {
result1 pacer.Pacer
}
GetPlayoutDelayConfigStub func() *livekit.PlayoutDelay
getPlayoutDelayConfigMutex sync.RWMutex
getPlayoutDelayConfigArgsForCall []struct {
}
getPlayoutDelayConfigReturns struct {
result1 *livekit.PlayoutDelay
}
getPlayoutDelayConfigReturnsOnCall map[int]struct {
result1 *livekit.PlayoutDelay
}
GetPublishedTrackStub func(livekit.TrackID) types.MediaTrack
getPublishedTrackMutex sync.RWMutex
getPublishedTrackArgsForCall []struct {
@@ -772,6 +782,16 @@ type FakeLocalParticipant struct {
arg2 livekit.TrackID
arg3 bool
}
SupportSyncStreamIDStub func() bool
supportSyncStreamIDMutex sync.RWMutex
supportSyncStreamIDArgsForCall []struct {
}
supportSyncStreamIDReturns struct {
result1 bool
}
supportSyncStreamIDReturnsOnCall map[int]struct {
result1 bool
}
ToProtoStub func() *livekit.ParticipantInfo
toProtoMutex sync.RWMutex
toProtoArgsForCall []struct {
@@ -2149,6 +2169,59 @@ func (fake *FakeLocalParticipant) GetPacerReturnsOnCall(i int, result1 pacer.Pac
}{result1}
}
func (fake *FakeLocalParticipant) GetPlayoutDelayConfig() *livekit.PlayoutDelay {
fake.getPlayoutDelayConfigMutex.Lock()
ret, specificReturn := fake.getPlayoutDelayConfigReturnsOnCall[len(fake.getPlayoutDelayConfigArgsForCall)]
fake.getPlayoutDelayConfigArgsForCall = append(fake.getPlayoutDelayConfigArgsForCall, struct {
}{})
stub := fake.GetPlayoutDelayConfigStub
fakeReturns := fake.getPlayoutDelayConfigReturns
fake.recordInvocation("GetPlayoutDelayConfig", []interface{}{})
fake.getPlayoutDelayConfigMutex.Unlock()
if stub != nil {
return stub()
}
if specificReturn {
return ret.result1
}
return fakeReturns.result1
}
func (fake *FakeLocalParticipant) GetPlayoutDelayConfigCallCount() int {
fake.getPlayoutDelayConfigMutex.RLock()
defer fake.getPlayoutDelayConfigMutex.RUnlock()
return len(fake.getPlayoutDelayConfigArgsForCall)
}
func (fake *FakeLocalParticipant) GetPlayoutDelayConfigCalls(stub func() *livekit.PlayoutDelay) {
fake.getPlayoutDelayConfigMutex.Lock()
defer fake.getPlayoutDelayConfigMutex.Unlock()
fake.GetPlayoutDelayConfigStub = stub
}
func (fake *FakeLocalParticipant) GetPlayoutDelayConfigReturns(result1 *livekit.PlayoutDelay) {
fake.getPlayoutDelayConfigMutex.Lock()
defer fake.getPlayoutDelayConfigMutex.Unlock()
fake.GetPlayoutDelayConfigStub = nil
fake.getPlayoutDelayConfigReturns = struct {
result1 *livekit.PlayoutDelay
}{result1}
}
func (fake *FakeLocalParticipant) GetPlayoutDelayConfigReturnsOnCall(i int, result1 *livekit.PlayoutDelay) {
fake.getPlayoutDelayConfigMutex.Lock()
defer fake.getPlayoutDelayConfigMutex.Unlock()
fake.GetPlayoutDelayConfigStub = nil
if fake.getPlayoutDelayConfigReturnsOnCall == nil {
fake.getPlayoutDelayConfigReturnsOnCall = make(map[int]struct {
result1 *livekit.PlayoutDelay
})
}
fake.getPlayoutDelayConfigReturnsOnCall[i] = struct {
result1 *livekit.PlayoutDelay
}{result1}
}
func (fake *FakeLocalParticipant) GetPublishedTrack(arg1 livekit.TrackID) types.MediaTrack {
fake.getPublishedTrackMutex.Lock()
ret, specificReturn := fake.getPublishedTrackReturnsOnCall[len(fake.getPublishedTrackArgsForCall)]
@@ -5008,6 +5081,59 @@ func (fake *FakeLocalParticipant) SubscriptionPermissionUpdateArgsForCall(i int)
return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3
}
func (fake *FakeLocalParticipant) SupportSyncStreamID() bool {
fake.supportSyncStreamIDMutex.Lock()
ret, specificReturn := fake.supportSyncStreamIDReturnsOnCall[len(fake.supportSyncStreamIDArgsForCall)]
fake.supportSyncStreamIDArgsForCall = append(fake.supportSyncStreamIDArgsForCall, struct {
}{})
stub := fake.SupportSyncStreamIDStub
fakeReturns := fake.supportSyncStreamIDReturns
fake.recordInvocation("SupportSyncStreamID", []interface{}{})
fake.supportSyncStreamIDMutex.Unlock()
if stub != nil {
return stub()
}
if specificReturn {
return ret.result1
}
return fakeReturns.result1
}
func (fake *FakeLocalParticipant) SupportSyncStreamIDCallCount() int {
fake.supportSyncStreamIDMutex.RLock()
defer fake.supportSyncStreamIDMutex.RUnlock()
return len(fake.supportSyncStreamIDArgsForCall)
}
func (fake *FakeLocalParticipant) SupportSyncStreamIDCalls(stub func() bool) {
fake.supportSyncStreamIDMutex.Lock()
defer fake.supportSyncStreamIDMutex.Unlock()
fake.SupportSyncStreamIDStub = stub
}
func (fake *FakeLocalParticipant) SupportSyncStreamIDReturns(result1 bool) {
fake.supportSyncStreamIDMutex.Lock()
defer fake.supportSyncStreamIDMutex.Unlock()
fake.SupportSyncStreamIDStub = nil
fake.supportSyncStreamIDReturns = struct {
result1 bool
}{result1}
}
func (fake *FakeLocalParticipant) SupportSyncStreamIDReturnsOnCall(i int, result1 bool) {
fake.supportSyncStreamIDMutex.Lock()
defer fake.supportSyncStreamIDMutex.Unlock()
fake.SupportSyncStreamIDStub = nil
if fake.supportSyncStreamIDReturnsOnCall == nil {
fake.supportSyncStreamIDReturnsOnCall = make(map[int]struct {
result1 bool
})
}
fake.supportSyncStreamIDReturnsOnCall[i] = struct {
result1 bool
}{result1}
}
func (fake *FakeLocalParticipant) ToProto() *livekit.ParticipantInfo {
fake.toProtoMutex.Lock()
ret, specificReturn := fake.toProtoReturnsOnCall[len(fake.toProtoArgsForCall)]
@@ -5703,6 +5829,8 @@ func (fake *FakeLocalParticipant) Invocations() map[string][][]interface{} {
defer fake.getLoggerMutex.RUnlock()
fake.getPacerMutex.RLock()
defer fake.getPacerMutex.RUnlock()
fake.getPlayoutDelayConfigMutex.RLock()
defer fake.getPlayoutDelayConfigMutex.RUnlock()
fake.getPublishedTrackMutex.RLock()
defer fake.getPublishedTrackMutex.RUnlock()
fake.getPublishedTracksMutex.RLock()
@@ -5831,6 +5959,8 @@ func (fake *FakeLocalParticipant) Invocations() map[string][][]interface{} {
defer fake.subscriptionPermissionMutex.RUnlock()
fake.subscriptionPermissionUpdateMutex.RLock()
defer fake.subscriptionPermissionUpdateMutex.RUnlock()
fake.supportSyncStreamIDMutex.RLock()
defer fake.supportSyncStreamIDMutex.RUnlock()
fake.toProtoMutex.RLock()
defer fake.toProtoMutex.RUnlock()
fake.toProtoWithVersionMutex.RLock()
@@ -236,6 +236,16 @@ type FakeMediaTrack struct {
sourceReturnsOnCall map[int]struct {
result1 livekit.TrackSource
}
StreamStub func() string
streamMutex sync.RWMutex
streamArgsForCall []struct {
}
streamReturns struct {
result1 string
}
streamReturnsOnCall map[int]struct {
result1 string
}
ToProtoStub func() *livekit.TrackInfo
toProtoMutex sync.RWMutex
toProtoArgsForCall []struct {
@@ -1474,6 +1484,59 @@ func (fake *FakeMediaTrack) SourceReturnsOnCall(i int, result1 livekit.TrackSour
}{result1}
}
func (fake *FakeMediaTrack) Stream() string {
fake.streamMutex.Lock()
ret, specificReturn := fake.streamReturnsOnCall[len(fake.streamArgsForCall)]
fake.streamArgsForCall = append(fake.streamArgsForCall, struct {
}{})
stub := fake.StreamStub
fakeReturns := fake.streamReturns
fake.recordInvocation("Stream", []interface{}{})
fake.streamMutex.Unlock()
if stub != nil {
return stub()
}
if specificReturn {
return ret.result1
}
return fakeReturns.result1
}
func (fake *FakeMediaTrack) StreamCallCount() int {
fake.streamMutex.RLock()
defer fake.streamMutex.RUnlock()
return len(fake.streamArgsForCall)
}
func (fake *FakeMediaTrack) StreamCalls(stub func() string) {
fake.streamMutex.Lock()
defer fake.streamMutex.Unlock()
fake.StreamStub = stub
}
func (fake *FakeMediaTrack) StreamReturns(result1 string) {
fake.streamMutex.Lock()
defer fake.streamMutex.Unlock()
fake.StreamStub = nil
fake.streamReturns = struct {
result1 string
}{result1}
}
func (fake *FakeMediaTrack) StreamReturnsOnCall(i int, result1 string) {
fake.streamMutex.Lock()
defer fake.streamMutex.Unlock()
fake.StreamStub = nil
if fake.streamReturnsOnCall == nil {
fake.streamReturnsOnCall = make(map[int]struct {
result1 string
})
}
fake.streamReturnsOnCall[i] = struct {
result1 string
}{result1}
}
func (fake *FakeMediaTrack) ToProto() *livekit.TrackInfo {
fake.toProtoMutex.Lock()
ret, specificReturn := fake.toProtoReturnsOnCall[len(fake.toProtoArgsForCall)]
@@ -1615,6 +1678,8 @@ func (fake *FakeMediaTrack) Invocations() map[string][][]interface{} {
defer fake.setMutedMutex.RUnlock()
fake.sourceMutex.RLock()
defer fake.sourceMutex.RUnlock()
fake.streamMutex.RLock()
defer fake.streamMutex.RUnlock()
fake.toProtoMutex.RLock()
defer fake.toProtoMutex.RUnlock()
fake.updateVideoLayersMutex.RLock()
+19
View File
@@ -42,6 +42,25 @@ func PackStreamID(participantID livekit.ParticipantID, trackID livekit.TrackID)
return string(participantID) + trackIdSeparator + string(trackID)
}
func PackSyncStreamID(participantID livekit.ParticipantID, stream string) string {
return string(participantID) + trackIdSeparator + stream
}
func StreamFromTrackSource(source livekit.TrackSource) string {
// group camera/mic, screenshare/audio together
switch source {
case livekit.TrackSource_SCREEN_SHARE:
return "screen"
case livekit.TrackSource_SCREEN_SHARE_AUDIO:
return "screen"
case livekit.TrackSource_CAMERA:
return "camera"
case livekit.TrackSource_MICROPHONE:
return "camera"
}
return "unknown"
}
func PackDataTrackLabel(participantID livekit.ParticipantID, trackID livekit.TrackID, label string) string {
return string(participantID) + trackIdSeparator + string(trackID) + trackIdSeparator + label
}
+5
View File
@@ -151,4 +151,9 @@ func applyDefaultRoomConfig(room *livekit.Room, conf *config.RoomConfig) {
FmtpLine: codec.FmtpLine,
})
}
room.PlayoutDelay = &livekit.PlayoutDelay{
Enabled: conf.PlayoutDelay.Enabled,
Min: uint32(conf.PlayoutDelay.Min),
Max: uint32(conf.PlayoutDelay.Max),
}
}
+1
View File
@@ -390,6 +390,7 @@ func (r *RoomManager) StartSession(
SubscriberAllowPause: subscriberAllowPause,
SubscriptionLimitAudio: r.config.Limit.SubscriptionLimitAudio,
SubscriptionLimitVideo: r.config.Limit.SubscriptionLimitVideo,
PlayoutDelay: protoRoom.PlayoutDelay,
})
if err != nil {
return err
+10 -2
View File
@@ -361,8 +361,16 @@ func (s *RTCService) ServeHTTP(w http.ResponseWriter, r *http.Request) {
req, count, err := sigConn.ReadRequest()
if err != nil {
// normal/expected closure
if err == io.EOF || strings.HasSuffix(err.Error(), "use of closed network connection") ||
websocket.IsCloseError(err, websocket.CloseAbnormalClosure, websocket.CloseGoingAway, websocket.CloseNormalClosure, websocket.CloseNoStatusReceived) {
if err == io.EOF ||
strings.HasSuffix(err.Error(), "use of closed network connection") ||
strings.HasSuffix(err.Error(), "connection reset by peer") ||
websocket.IsCloseError(
err,
websocket.CloseAbnormalClosure,
websocket.CloseGoingAway,
websocket.CloseNormalClosure,
websocket.CloseNoStatusReceived,
) {
pLogger.Infow("exit ws read loop for closed connection", "connID", cr.ConnectionID, "wsError", err)
} else {
pLogger.Errorw("error reading from websocket", err, "connID", cr.ConnectionID)
+1 -1
View File
@@ -181,7 +181,7 @@ func (b *Buffer) Bind(params webrtc.RTPParameters, codec webrtc.RTPCodecCapabili
for _, ext := range params.HeaderExtensions {
switch ext.URI {
case dd.ExtensionUrl:
case dd.ExtensionURI:
b.ddExt = uint8(ext.ID)
frc := NewFrameRateCalculatorDD(b.clockRate, b.logger)
for i := range b.frameRateCalculator {
+7 -20
View File
@@ -807,8 +807,10 @@ func (r *RTPStats) SetRtcpSenderReportData(srData *RTCPSenderReportData) {
r.logger.Infow(
"received sender report, out-of-order, resetting",
"prevTSExt", r.srNewest.RTPTimestampExt,
"prevRTP", r.srNewest.RTPTimestamp,
"prevNTP", r.srNewest.NTPTimestamp.Time().String(),
"currTSExt", srDataCopy.RTPTimestampExt,
"currRTP", srDataCopy.RTPTimestamp,
"currNTP", srDataCopy.NTPTimestamp.Time().String(),
)
r.srFirst = &srDataCopy
@@ -914,7 +916,9 @@ func (r *RTPStats) GetRtcpSenderReport(ssrc uint32, calculatedClockRate uint32)
}
if r.srNewest != nil && nowRTPExt < r.srNewest.RTPTimestampExt {
// If report being generated is behind, use the time different and clock rate of codec to produce next report.
// If report being generated is behind, use the time difference and
// clock rate of codec to produce next report.
//
// Current report could be behind due to the following
// - Publisher pacing
// - Due to above, report from publisher side is ahead of packet timestamps.
@@ -926,8 +930,10 @@ func (r *RTPStats) GetRtcpSenderReport(ssrc uint32, calculatedClockRate uint32)
r.logger.Infow(
"sending sender report, out-of-order, repairing",
"prevTSExt", r.srNewest.RTPTimestampExt,
"prevRTP", r.srNewest.RTPTimestamp,
"prevNTP", r.srNewest.NTPTimestamp.Time().String(),
"currTSExt", nowRTPExt,
"currRTP", nowRTP,
"currNTP", nowNTP.Time().String(),
)
ntpDiffSinceLast := nowNTP.Time().Sub(r.srNewest.NTPTimestamp.Time())
@@ -981,25 +987,6 @@ func (r *RTPStats) GetRtcpSenderReport(ssrc uint32, calculatedClockRate uint32)
"nowRTPExt", nowRTPExt,
"nowRTPExtUsingRate", nowRTPExtUsingRate,
)
} else {
packetDriftResult, reportDriftResult := r.getDrift()
r.logger.Debugw(
"sending sender report",
"ntp", nowNTP.Time().String(),
"rtp", nowRTP,
"departure", now.String(),
"ntpDiffSinceLast", ntpDiffSinceLast.Seconds(),
"rtpDiffSinceLast", int32(rtpDiffSinceLast),
"departureDiffSinceLast", departureDiffSinceLast.Seconds(),
"expectedTimeDiffSinceLast", expectedTimeDiffSinceLast,
"packetDrift", packetDriftResult.String(),
"reportDrift", reportDriftResult.String(),
"highestTS", r.highestTS,
"highestTime", r.highestTime.String(),
"calculatedClockRate", calculatedClockRate,
"nowRTPExt", nowRTPExt,
"nowRTPExtUsingRate", nowRTPExtUsingRate,
)
}
return &rtcp.SenderReport{
@@ -69,7 +69,7 @@ const (
AllChainsAreActive = uint32(0)
ExtensionUrl = "https://aomediacodec.github.io/av1-rtp-spec/#dependency-descriptor-rtp-header-extension"
ExtensionURI = "https://aomediacodec.github.io/av1-rtp-spec/#dependency-descriptor-rtp-header-extension"
)
// ------------------------------------------------------------------------------
+129 -107
View File
@@ -37,6 +37,7 @@ import (
"github.com/livekit/livekit-server/pkg/sfu/connectionquality"
dd "github.com/livekit/livekit-server/pkg/sfu/dependencydescriptor"
"github.com/livekit/livekit-server/pkg/sfu/pacer"
"github.com/livekit/livekit-server/pkg/sfu/rtpextension"
)
// TrackSender defines an interface send media to remote peer
@@ -184,6 +185,19 @@ type DownTrackStreamAllocatorListener interface {
type ReceiverReportListener func(dt *DownTrack, report *rtcp.ReceiverReport)
type DowntrackParams struct {
Codecs []webrtc.RTPCodecParameters
Receiver TrackReceiver
BufferFactory *buffer.Factory
SubID livekit.ParticipantID
StreamID string
MaxTrack int
PlayoutDelayLimit *livekit.PlayoutDelay
Pacer pacer.Pacer
Logger logger.Logger
Trailer []byte
}
// DownTrack implements TrackLocal, is the track used to write packets
// to SFU Subscriber, the track handle the packets for simple, simulcast
// and SVC Publisher.
@@ -193,17 +207,13 @@ type ReceiverReportListener func(dt *DownTrack, report *rtcp.ReceiverReport)
// - closed
// once closed, a DownTrack cannot be re-used.
type DownTrack struct {
logger logger.Logger
id livekit.TrackID
subscriberID livekit.ParticipantID
kind webrtc.RTPCodecType
mime string
ssrc uint32
streamID string
maxTrack int
payloadType uint8
sequencer *sequencer
bufferFactory *buffer.Factory
params DowntrackParams
id livekit.TrackID
kind webrtc.RTPCodecType
mime string
ssrc uint32
payloadType uint8
sequencer *sequencer
forwarder *Forwarder
@@ -212,8 +222,8 @@ type DownTrack struct {
absSendTimeExtID int
transportWideExtID int
dependencyDescriptorExtID int
receiver TrackReceiver
transceiver *webrtc.RTPTransceiver
playoutDelayExtID int
transceiver atomic.Pointer[webrtc.RTPTransceiver]
writeStream webrtc.TrackLocalWriter
rtcpReader *buffer.RTCPReader
@@ -251,12 +261,13 @@ type DownTrack struct {
bytesSent atomic.Uint32
bytesRetransmitted atomic.Uint32
playoutDelayBytes atomic.Value //bytes of marshalled playout delay
playoudDelayAcked atomic.Bool
pacer pacer.Pacer
maxLayerNotifierCh chan struct{}
trailer []byte
cbMu sync.RWMutex
onStatsUpdate func(dt *DownTrack, stat *livekit.AnalyticsStat)
onMaxSubscribedLayerChanged func(dt *DownTrack, layer int32)
@@ -265,16 +276,8 @@ type DownTrack struct {
}
// NewDownTrack returns a DownTrack.
func NewDownTrack(
codecs []webrtc.RTPCodecParameters,
r TrackReceiver,
bf *buffer.Factory,
subID livekit.ParticipantID,
mt int,
pacer pacer.Pacer,
trailer []byte,
logger logger.Logger,
) (*DownTrack, error) {
func NewDownTrack(params DowntrackParams) (*DownTrack, error) {
codecs := params.Codecs
var kind webrtc.RTPCodecType
switch {
case strings.HasPrefix(codecs[0].MimeType, "audio/"):
@@ -286,36 +289,25 @@ func NewDownTrack(
}
d := &DownTrack{
logger: logger,
id: r.TrackID(),
subscriberID: subID,
maxTrack: mt,
streamID: r.StreamID(),
bufferFactory: bf,
receiver: r,
params: params,
id: params.Receiver.TrackID(),
upstreamCodecs: codecs,
kind: kind,
codec: codecs[0].RTPCodecCapability,
pacer: pacer,
trailer: trailer,
pacer: params.Pacer,
maxLayerNotifierCh: make(chan struct{}, 20),
}
d.forwarder = NewForwarder(
d.kind,
d.logger,
d.receiver.GetReferenceLayerRTPTimestamp,
params.Logger,
d.params.Receiver.GetReferenceLayerRTPTimestamp,
d.getExpectedRTPTimestamp,
)
d.forwarder.OnParkedLayerExpired(func() {
if sal := d.getStreamAllocatorListener(); sal != nil {
sal.OnSubscriptionChanged(d)
}
})
d.rtpStats = buffer.NewRTPStats(buffer.RTPStatsParams{
ClockRate: d.codec.ClockRate,
IsReceiverReportDriven: true,
Logger: d.logger,
Logger: params.Logger,
})
d.deltaStatsSnapshotId = d.rtpStats.NewSnapshotId()
d.deltaStatsOverriddenSnapshotId = d.rtpStats.NewSnapshotId()
@@ -326,7 +318,7 @@ func NewDownTrack(
GetDeltaStats: d.getDeltaStats,
GetDeltaStatsOverridden: d.getDeltaStatsOverridden,
GetLastReceiverReportTime: func() time.Time { return d.rtpStats.LastReceiverReport() },
Logger: d.logger.WithValues("direction", "down"),
Logger: params.Logger.WithValues("direction", "down"),
})
d.connectionStats.OnStatsUpdate(func(_cs *connectionquality.ConnectionStats, stat *livekit.AnalyticsStat) {
if onStatsUpdate := d.getOnStatsUpdate(); onStatsUpdate != nil {
@@ -334,6 +326,18 @@ func NewDownTrack(
}
})
// set initial playout delay to minimum value
if d.params.PlayoutDelayLimit.GetEnabled() && d.params.PlayoutDelayLimit.GetMin() > 0 {
delay := rtpextension.PlayoutDelayFromValue(
uint16(d.params.PlayoutDelayLimit.GetMin()),
uint16(d.params.PlayoutDelayLimit.GetMax()),
)
b, err := delay.Marshal()
if err == nil {
d.playoutDelayBytes.Store(b)
}
}
if d.kind == webrtc.RTPCodecTypeVideo {
go d.maxLayerNotifierWorker()
}
@@ -363,7 +367,7 @@ func (d *DownTrack) Bind(t webrtc.TrackLocalContext) (webrtc.RTPCodecParameters,
err := webrtc.ErrUnsupportedCodec
onBinding := d.onBinding
d.bindLock.Unlock()
d.logger.Infow("bind error for unsupported codec", "codecs", d.upstreamCodecs, "remoteParameters", t.CodecParameters())
d.params.Logger.Infow("bind error for unsupported codec", "codecs", d.upstreamCodecs, "remoteParameters", t.CodecParameters())
if onBinding != nil {
onBinding(err)
}
@@ -372,17 +376,17 @@ func (d *DownTrack) Bind(t webrtc.TrackLocalContext) (webrtc.RTPCodecParameters,
// if a downtrack is closed before bind, it already unsubscribed from client, don't do subsequent operation and return here.
if d.IsClosed() {
d.logger.Debugw("DownTrack closed before bind")
d.params.Logger.Debugw("DownTrack closed before bind")
d.bindLock.Unlock()
return codec, nil
}
d.logger.Debugw("DownTrack.Bind", "codecs", d.upstreamCodecs, "matchCodec", codec, "ssrc", t.SSRC())
d.params.Logger.Debugw("DownTrack.Bind", "codecs", d.upstreamCodecs, "matchCodec", codec, "ssrc", t.SSRC())
d.ssrc = uint32(t.SSRC())
d.payloadType = uint8(codec.PayloadType)
d.writeStream = t.WriteStream()
d.mime = strings.ToLower(codec.MimeType)
if rr := d.bufferFactory.GetOrNew(packetio.RTCPBufferPacket, uint32(t.SSRC())).(*buffer.RTCPReader); rr != nil {
if rr := d.params.BufferFactory.GetOrNew(packetio.RTCPBufferPacket, uint32(t.SSRC())).(*buffer.RTCPReader); rr != nil {
rr.OnPacket(func(pkt []byte) {
d.handleRTCP(pkt)
})
@@ -390,9 +394,9 @@ func (d *DownTrack) Bind(t webrtc.TrackLocalContext) (webrtc.RTPCodecParameters,
}
if d.kind == webrtc.RTPCodecTypeAudio {
d.sequencer = newSequencer(d.maxTrack, 0, d.logger)
d.sequencer = newSequencer(d.params.MaxTrack, 0, d.params.Logger)
} else {
d.sequencer = newSequencer(d.maxTrack, maxPadding, d.logger)
d.sequencer = newSequencer(d.params.MaxTrack, maxPadding, d.params.Logger)
}
d.codec = codec.RTPCodecCapability
@@ -402,9 +406,18 @@ func (d *DownTrack) Bind(t webrtc.TrackLocalContext) (webrtc.RTPCodecParameters,
d.bound.Store(true)
d.bindLock.Unlock()
d.forwarder.DetermineCodec(d.codec, d.receiver.HeaderExtensions())
// Bind is called under RTPSender.mu lock, call the RTPSender.GetParameters in goroutine to avoid deadlock
go func() {
if tr := d.transceiver.Load(); tr != nil {
extensions := tr.Sender().GetParameters().HeaderExtensions
d.params.Logger.Debugw("negotiated downtrack extensions", "extensions", extensions)
d.SetRTPHeaderExtensions(extensions)
}
}()
d.logger.Debugw("downtrack bound")
d.forwarder.DetermineCodec(d.codec, d.params.Receiver.HeaderExtensions())
d.params.Logger.Debugw("downtrack bound")
d.onBindAndConnected()
return codec, nil
@@ -418,7 +431,7 @@ func (d *DownTrack) Unbind(_ webrtc.TrackLocalContext) error {
}
func (d *DownTrack) TrackInfoAvailable() {
ti := d.receiver.TrackInfo()
ti := d.params.Receiver.TrackInfo()
if ti == nil {
return
}
@@ -496,9 +509,9 @@ func (d *DownTrack) ID() string { return string(d.id) }
func (d *DownTrack) Codec() webrtc.RTPCodecCapability { return d.codec }
// StreamID is the group this track belongs too. This must be unique
func (d *DownTrack) StreamID() string { return d.streamID }
func (d *DownTrack) StreamID() string { return d.params.StreamID }
func (d *DownTrack) SubscriberID() livekit.ParticipantID { return d.subscriberID }
func (d *DownTrack) SubscriberID() livekit.ParticipantID { return d.params.SubID }
// Sets RTP header extensions for this track
func (d *DownTrack) SetRTPHeaderExtensions(rtpHeaderExtensions []webrtc.RTPHeaderExtensionParameter) {
@@ -506,10 +519,12 @@ func (d *DownTrack) SetRTPHeaderExtensions(rtpHeaderExtensions []webrtc.RTPHeade
switch ext.URI {
case sdp.ABSSendTimeURI:
d.absSendTimeExtID = ext.ID
case dd.ExtensionURI:
d.dependencyDescriptorExtID = ext.ID
case rtpextension.PlayoutDelayURI:
d.playoutDelayExtID = ext.ID
case sdp.TransportCCURI:
d.transportWideExtID = ext.ID
case dd.ExtensionUrl:
d.dependencyDescriptorExtID = ext.ID
}
}
}
@@ -529,18 +544,18 @@ func (d *DownTrack) SSRC() uint32 {
}
func (d *DownTrack) Stop() error {
if d.transceiver != nil {
return d.transceiver.Stop()
if tr := d.transceiver.Load(); tr != nil {
return tr.Stop()
}
return errors.New("downtrack transceiver does not exist")
}
func (d *DownTrack) SetTransceiver(transceiver *webrtc.RTPTransceiver) {
d.transceiver = transceiver
d.transceiver.Store(transceiver)
}
func (d *DownTrack) GetTransceiver() *webrtc.RTPTransceiver {
return d.transceiver
return d.transceiver.Load()
}
func (d *DownTrack) maybeStartKeyFrameRequester() {
@@ -552,7 +567,6 @@ func (d *DownTrack) maybeStartKeyFrameRequester() {
//
d.stopKeyFrameRequester()
// SVC-TODO : don't need pli/lrr when layer comes down
locked, layer := d.forwarder.CheckSync()
if !locked {
go d.keyFrameRequester(d.keyFrameRequestGeneration.Load(), layer)
@@ -585,8 +599,8 @@ func (d *DownTrack) keyFrameRequester(generation uint32, layer int32) {
}
if d.connected.Load() {
d.logger.Debugw("sending PLI for layer lock", "generation", generation, "layer", layer)
d.receiver.SendPLI(layer, false)
d.params.Logger.Debugw("sending PLI for layer lock", "generation", generation, "layer", layer)
d.params.Receiver.SendPLI(layer, false)
d.rtpStats.UpdateLayerLockPliAndTime(1)
}
@@ -606,7 +620,7 @@ func (d *DownTrack) postMaxLayerNotifierEvent() {
select {
case d.maxLayerNotifierCh <- struct{}{}:
default:
d.logger.Warnw("max layer notifier event queue full", nil)
d.params.Logger.Warnw("max layer notifier event queue full", nil)
}
}
@@ -620,7 +634,7 @@ func (d *DownTrack) maxLayerNotifierWorker() {
maxLayerSpatial = d.forwarder.GetMaxSubscribedSpatial()
}
if onMaxSubscribedLayerChanged := d.getOnMaxLayerChanged(); onMaxSubscribedLayerChanged != nil {
d.logger.Infow("max subscribed layer changed", "maxLayerSpatial", maxLayerSpatial)
d.params.Logger.Infow("max subscribed layer changed", "maxLayerSpatial", maxLayerSpatial)
onMaxSubscribedLayerChanged(d, maxLayerSpatial)
}
}
@@ -635,7 +649,7 @@ func (d *DownTrack) WriteRTP(extPkt *buffer.ExtPacket, layer int32) error {
tp, err := d.forwarder.GetTranslationParams(extPkt, layer)
if tp.shouldDrop {
if err != nil {
d.logger.Errorw("write rtp packet failed", err)
d.params.Logger.Errorw("write rtp packet failed", err)
}
return err
}
@@ -655,13 +669,19 @@ func (d *DownTrack) WriteRTP(extPkt *buffer.ExtPacket, layer int32) error {
hdr, err := d.getTranslatedRTPHeader(extPkt, tp)
if err != nil {
d.logger.Errorw("write rtp packet failed", err)
d.params.Logger.Errorw("write rtp packet failed", err)
if pool != nil {
PacketFactory.Put(pool)
}
return err
}
extensions := []pacer.ExtensionData{{ID: uint8(d.dependencyDescriptorExtID), Payload: tp.ddBytes}}
if d.playoutDelayExtID != 0 && !d.playoudDelayAcked.Load() {
if val := d.playoutDelayBytes.Load(); val != nil {
extensions = append(extensions, pacer.ExtensionData{ID: uint8(d.playoutDelayExtID), Payload: val.([]byte)})
}
}
if d.sequencer != nil {
d.sequencer.push(
extPkt.Packet.SequenceNumber,
@@ -676,7 +696,7 @@ func (d *DownTrack) WriteRTP(extPkt *buffer.ExtPacket, layer int32) error {
d.pacer.Enqueue(pacer.Packet{
Header: hdr,
Extensions: []pacer.ExtensionData{{ID: uint8(d.dependencyDescriptorExtID), Payload: tp.ddBytes}},
Extensions: extensions,
Payload: payload,
AbsSendTimeExtID: uint8(d.absSendTimeExtID),
TransportWideExtID: uint8(d.transportWideExtID),
@@ -870,7 +890,7 @@ func (d *DownTrack) CloseWithFlush(flush bool) {
}
d.bindLock.Lock()
d.logger.Debugw("close down track", "flushBlankFrame", flush)
d.params.Logger.Debugw("close down track", "flushBlankFrame", flush)
if d.bound.Load() {
if d.forwarder != nil {
d.forwarder.Mute(true)
@@ -894,12 +914,12 @@ func (d *DownTrack) CloseWithFlush(flush bool) {
}
d.bound.Store(false)
d.logger.Debugw("closing sender", "kind", d.kind)
d.params.Logger.Debugw("closing sender", "kind", d.kind)
}
d.receiver.DeleteDownTrack(d.subscriberID)
d.params.Receiver.DeleteDownTrack(d.params.SubID)
if d.rtcpReader != nil && flush {
d.logger.Debugw("downtrack close rtcp reader")
d.params.Logger.Debugw("downtrack close rtcp reader")
d.rtcpReader.Close()
d.rtcpReader.OnPacket(nil)
}
@@ -907,7 +927,7 @@ func (d *DownTrack) CloseWithFlush(flush bool) {
d.bindLock.Unlock()
d.connectionStats.Close()
d.rtpStats.Stop()
d.logger.Infow("rtp stats", "direction", "downstream", "mime", d.mime, "ssrc", d.ssrc, "stats", d.rtpStats.ToString())
d.params.Logger.Infow("rtp stats", "direction", "downstream", "mime", d.mime, "ssrc", d.ssrc, "stats", d.rtpStats.ToString())
close(d.maxLayerNotifierCh)
@@ -1089,17 +1109,17 @@ func (d *DownTrack) IsDeficient() bool {
}
func (d *DownTrack) BandwidthRequested() int64 {
_, brs := d.receiver.GetLayeredBitrate()
_, brs := d.params.Receiver.GetLayeredBitrate()
return d.forwarder.BandwidthRequested(brs)
}
func (d *DownTrack) DistanceToDesired() float64 {
al, brs := d.receiver.GetLayeredBitrate()
al, brs := d.params.Receiver.GetLayeredBitrate()
return d.forwarder.DistanceToDesired(al, brs)
}
func (d *DownTrack) AllocateOptimal(allowOvershoot bool) VideoAllocation {
al, brs := d.receiver.GetLayeredBitrate()
al, brs := d.params.Receiver.GetLayeredBitrate()
allocation := d.forwarder.AllocateOptimal(al, brs, allowOvershoot)
d.maybeStartKeyFrameRequester()
d.maybeAddTransition(allocation.BandwidthNeeded, allocation.DistanceToDesired, allocation.PauseReason)
@@ -1107,7 +1127,7 @@ func (d *DownTrack) AllocateOptimal(allowOvershoot bool) VideoAllocation {
}
func (d *DownTrack) ProvisionalAllocatePrepare() {
al, brs := d.receiver.GetLayeredBitrate()
al, brs := d.params.Receiver.GetLayeredBitrate()
d.forwarder.ProvisionalAllocatePrepare(al, brs)
}
@@ -1121,13 +1141,13 @@ func (d *DownTrack) ProvisionalAllocate(availableChannelCapacity int64, layers b
func (d *DownTrack) ProvisionalAllocateGetCooperativeTransition(allowOvershoot bool) VideoTransition {
transition := d.forwarder.ProvisionalAllocateGetCooperativeTransition(allowOvershoot)
d.logger.Debugw("stream: cooperative transition", "transition", transition)
d.params.Logger.Debugw("stream: cooperative transition", "transition", transition)
return transition
}
func (d *DownTrack) ProvisionalAllocateGetBestWeightedTransition() VideoTransition {
transition := d.forwarder.ProvisionalAllocateGetBestWeightedTransition()
d.logger.Debugw("stream: best weighted transition", "transition", transition)
d.params.Logger.Debugw("stream: best weighted transition", "transition", transition)
return transition
}
@@ -1139,7 +1159,7 @@ func (d *DownTrack) ProvisionalAllocateCommit() VideoAllocation {
}
func (d *DownTrack) AllocateNextHigher(availableChannelCapacity int64, allowOvershoot bool) (VideoAllocation, bool) {
al, brs := d.receiver.GetLayeredBitrate()
al, brs := d.params.Receiver.GetLayeredBitrate()
allocation, available := d.forwarder.AllocateNextHigher(availableChannelCapacity, al, brs, allowOvershoot)
d.maybeStartKeyFrameRequester()
d.maybeAddTransition(allocation.BandwidthNeeded, allocation.DistanceToDesired, allocation.PauseReason)
@@ -1147,14 +1167,14 @@ func (d *DownTrack) AllocateNextHigher(availableChannelCapacity int64, allowOver
}
func (d *DownTrack) GetNextHigherTransition(allowOvershoot bool) (VideoTransition, bool) {
_, brs := d.receiver.GetLayeredBitrate()
_, brs := d.params.Receiver.GetLayeredBitrate()
transition, available := d.forwarder.GetNextHigherTransition(brs, allowOvershoot)
d.logger.Debugw("stream: get next higher layer", "transition", transition, "available", available, "bitrates", brs)
d.params.Logger.Debugw("stream: get next higher layer", "transition", transition, "available", available, "bitrates", brs)
return transition, available
}
func (d *DownTrack) Pause() VideoAllocation {
al, brs := d.receiver.GetLayeredBitrate()
al, brs := d.params.Receiver.GetLayeredBitrate()
allocation := d.forwarder.Pause(al, brs)
d.maybeStartKeyFrameRequester()
d.maybeAddTransition(allocation.BandwidthNeeded, allocation.DistanceToDesired, allocation.PauseReason)
@@ -1166,7 +1186,7 @@ func (d *DownTrack) Resync() {
}
func (d *DownTrack) CreateSourceDescriptionChunks() []rtcp.SourceDescriptionChunk {
if !d.bound.Load() {
if !d.bound.Load() || d.transceiver.Load() == nil {
return nil
}
return []rtcp.SourceDescriptionChunk{
@@ -1174,13 +1194,13 @@ func (d *DownTrack) CreateSourceDescriptionChunks() []rtcp.SourceDescriptionChun
Source: d.ssrc,
Items: []rtcp.SourceDescriptionItem{{
Type: rtcp.SDESCNAME,
Text: d.streamID,
Text: d.params.StreamID,
}},
}, {
Source: d.ssrc,
Items: []rtcp.SourceDescriptionItem{{
Type: rtcp.SDESType(15),
Text: d.transceiver.Mid(),
Text: d.transceiver.Load().Mid(),
}},
},
}
@@ -1195,7 +1215,7 @@ func (d *DownTrack) CreateSenderReport() *rtcp.SenderReport {
if clockLayer == buffer.InvalidLayerSpatial {
clockLayer = d.forwarder.GetReferenceLayerSpatial()
}
return d.rtpStats.GetRtcpSenderReport(d.ssrc, d.receiver.GetCalculatedClockRate(clockLayer))
return d.rtpStats.GetRtcpSenderReport(d.ssrc, d.params.Receiver.GetCalculatedClockRate(clockLayer))
}
func (d *DownTrack) writeBlankFrameRTP(duration float32, generation uint32) chan struct{} {
@@ -1244,7 +1264,7 @@ func (d *DownTrack) writeBlankFrameRTP(duration float32, generation uint32) chan
snts, frameEndNeeded, err := d.forwarder.GetSnTsForBlankFrames(frameRate, 1)
if err != nil {
d.logger.Warnw("could not get SN/TS for blank frame", err)
d.params.Logger.Warnw("could not get SN/TS for blank frame", err)
close(done)
return
}
@@ -1263,7 +1283,7 @@ func (d *DownTrack) writeBlankFrameRTP(duration float32, generation uint32) chan
payload, err := getBlankFrame(frameEndNeeded)
if err != nil {
d.logger.Warnw("could not get blank frame", err)
d.params.Logger.Warnw("could not get blank frame", err)
close(done)
return
}
@@ -1294,13 +1314,13 @@ func (d *DownTrack) writeBlankFrameRTP(duration float32, generation uint32) chan
}
func (d *DownTrack) maybeAddTrailer(buf []byte) int {
if len(buf) < len(d.trailer) {
d.logger.Warnw("trailer too big", nil, "bufLen", len(buf), "trailerLen", len(d.trailer))
if len(buf) < len(d.params.Trailer) {
d.params.Logger.Warnw("trailer too big", nil, "bufLen", len(buf), "trailerLen", len(d.params.Trailer))
return 0
}
copy(buf, d.trailer)
return len(d.trailer)
copy(buf, d.params.Trailer)
return len(d.params.Trailer)
}
func (d *DownTrack) getOpusBlankFrame(_frameEndNeeded bool) ([]byte, error) {
@@ -1367,7 +1387,7 @@ func (d *DownTrack) getH264BlankFrame(_frameEndNeeded bool) ([]byte, error) {
func (d *DownTrack) handleRTCP(bytes []byte) {
pkts, err := rtcp.Unmarshal(bytes)
if err != nil {
d.logger.Errorw("unmarshal rtcp receiver packets err", err)
d.params.Logger.Errorw("unmarshal rtcp receiver packets err", err)
return
}
@@ -1376,8 +1396,8 @@ func (d *DownTrack) handleRTCP(bytes []byte) {
if pliOnce {
_, layer := d.forwarder.CheckSync()
if layer != buffer.InvalidLayerSpatial && !d.forwarder.IsAnyMuted() {
d.logger.Debugw("sending PLI RTCP", "layer", layer)
d.receiver.SendPLI(layer, false)
d.params.Logger.Debugw("sending PLI RTCP", "layer", layer)
d.params.Receiver.SendPLI(layer, false)
d.isNACKThrottled.Store(true)
d.rtpStats.UpdatePliTime()
pliOnce = false
@@ -1425,6 +1445,8 @@ func (d *DownTrack) handleRTCP(bytes []byte) {
if sal := d.getStreamAllocatorListener(); sal != nil {
sal.OnRTCPReceiverReport(d, r)
}
d.playoudDelayAcked.Store(true)
}
if len(rr.Reports) > 0 {
d.listenerLock.RLock()
@@ -1514,7 +1536,7 @@ func (d *DownTrack) retransmitPackets(nacks []uint16) {
})
pktBuff := *src
n, err := d.receiver.ReadRTP(pktBuff, uint8(meta.layer), meta.sourceSeqNo)
n, err := d.params.Receiver.ReadRTP(pktBuff, uint8(meta.layer), meta.sourceSeqNo)
if err != nil {
if err == io.EOF {
break
@@ -1529,7 +1551,7 @@ func (d *DownTrack) retransmitPackets(nacks []uint16) {
var pkt rtp.Packet
if err = pkt.Unmarshal(pktBuff[:n]); err != nil {
d.logger.Errorw("unmarshalling rtp packet failed in retransmit", err)
d.params.Logger.Errorw("unmarshalling rtp packet failed in retransmit", err)
continue
}
pkt.Header.Marker = meta.marker
@@ -1543,7 +1565,7 @@ func (d *DownTrack) retransmitPackets(nacks []uint16) {
if d.mime == "video/vp8" && len(pkt.Payload) > 0 && len(meta.codecBytes) != 0 {
var incomingVP8 buffer.VP8
if err = incomingVP8.Unmarshal(pkt.Payload); err != nil {
d.logger.Errorw("unmarshalling VP8 packet err", err)
d.params.Logger.Errorw("unmarshalling VP8 packet err", err)
PacketFactory.Put(pool)
continue
}
@@ -1634,9 +1656,9 @@ func (d *DownTrack) DebugInfo() map[string]interface{} {
}
return map[string]interface{}{
"SubscriberID": d.subscriberID,
"SubscriberID": d.params.SubID,
"TrackID": d.id,
"StreamID": d.streamID,
"StreamID": d.params.StreamID,
"SSRC": d.ssrc,
"MimeType": d.codec.MimeType,
"Bound": d.bound.Load(),
@@ -1698,7 +1720,7 @@ func (d *DownTrack) onBindAndConnected() {
if d.kind == webrtc.RTPCodecTypeVideo {
_, layer := d.forwarder.CheckSync()
if layer != buffer.InvalidLayerSpatial {
d.receiver.SendPLI(layer, true)
d.params.Receiver.SendPLI(layer, true)
}
}
@@ -1712,7 +1734,7 @@ func (d *DownTrack) sendPaddingOnMute() {
// let uptrack have chance to send packet before we send padding
time.Sleep(waitBeforeSendPaddingOnMute)
d.logger.Debugw("sending padding on mute")
d.params.Logger.Debugw("sending padding on mute")
if d.kind == webrtc.RTPCodecTypeVideo {
d.sendPaddingOnMuteForVideo()
} else if d.mime == "audio/opus" {
@@ -1742,7 +1764,7 @@ func (d *DownTrack) sendSilentFrameOnMuteForOpus() {
}
snts, _, err := d.forwarder.GetSnTsForBlankFrames(frameRate, 1)
if err != nil {
d.logger.Warnw("could not get SN/TS for blank frame", err)
d.params.Logger.Warnw("could not get SN/TS for blank frame", err)
return
}
for i := 0; i < len(snts); i++ {
@@ -1759,7 +1781,7 @@ func (d *DownTrack) sendSilentFrameOnMuteForOpus() {
payload, err := d.getOpusBlankFrame(false)
if err != nil {
d.logger.Warnw("could not get blank frame", err)
d.params.Logger.Warnw("could not get blank frame", err)
return
}
@@ -1806,7 +1828,7 @@ type sendPacketMetadata struct {
func (d *DownTrack) packetSent(md interface{}, hdr *rtp.Header, payloadSize int, sendTime time.Time, sendError error) {
spmd, ok := md.(sendPacketMetadata)
if !ok {
d.logger.Errorw("invalid send packet metadata", nil)
d.params.Logger.Errorw("invalid send packet metadata", nil)
return
}
@@ -1845,7 +1867,7 @@ func (d *DownTrack) packetSent(md interface{}, hdr *rtp.Header, payloadSize int,
if spmd.isKeyFrame {
d.isNACKThrottled.Store(false)
d.rtpStats.UpdateKeyFrame(1)
d.logger.Debugw(
d.params.Logger.Debugw(
"forwarded key frame",
"layer", spmd.layer,
"rtpsn", hdr.SequenceNumber,
+15 -116
View File
@@ -36,10 +36,9 @@ import (
// Forwarder
const (
FlagPauseOnDowngrade = true
FlagFilterRTX = true
TransitionCostSpatial = 10
ParkedLayerWaitDuration = 2 * time.Second
FlagPauseOnDowngrade = true
FlagFilterRTX = true
TransitionCostSpatial = 10
ResumeBehindThresholdSeconds = float64(0.1) // 100ms
LayerSwitchBehindThresholdSeconds = float64(0.05) // 50ms
@@ -124,7 +123,6 @@ type VideoAllocationProvisional struct {
Bitrates Bitrates
maxLayer buffer.VideoLayer
currentLayer buffer.VideoLayer
parkedLayer buffer.VideoLayer
allocatedLayer buffer.VideoLayer
}
@@ -199,8 +197,6 @@ type Forwarder struct {
referenceLayerSpatial int32
refTSOffset uint32
parkedLayerTimer *time.Timer
provisional *VideoAllocationProvisional
lastAllocation VideoAllocation
@@ -210,8 +206,6 @@ type Forwarder struct {
vls videolayerselector.VideoLayerSelector
codecMunger codecmunger.CodecMunger
onParkedLayerExpired func()
}
func NewForwarder(
@@ -266,20 +260,6 @@ func (f *Forwarder) SetMaxTemporalLayerSeen(maxTemporalLayerSeen int32) bool {
return true
}
func (f *Forwarder) OnParkedLayerExpired(fn func()) {
f.lock.Lock()
defer f.lock.Unlock()
f.onParkedLayerExpired = fn
}
func (f *Forwarder) getOnParkedLayerExpired() func() {
f.lock.RLock()
defer f.lock.RUnlock()
return f.onParkedLayerExpired
}
func (f *Forwarder) DetermineCodec(codec webrtc.RTPCodecCapability, extensions []webrtc.RTPHeaderExtensionParameter) {
f.lock.Lock()
defer f.lock.Unlock()
@@ -309,7 +289,7 @@ func (f *Forwarder) DetermineCodec(codec webrtc.RTPCodecCapability, extensions [
searchDone:
for _, ext := range extensions {
switch ext.URI {
case dd.ExtensionUrl:
case dd.ExtensionURI:
isDDAvailable = true
break searchDone
}
@@ -395,7 +375,7 @@ func (f *Forwarder) Mute(muted bool) bool {
// It could result in some bandwidth consumed for stream without visibility in
// the case of intentional mute.
if muted && f.isDeficientLocked() && f.lastAllocation.PauseReason == VideoPauseReasonBandwidth {
f.logger.Infow("ignoring forwarder mute, paused due to congestion")
f.logger.Debugw("ignoring forwarder mute, paused due to congestion")
return false
}
@@ -428,22 +408,10 @@ func (f *Forwarder) PubMute(pubMuted bool) bool {
f.logger.Debugw("setting forwarder pub mute", "pubMuted", pubMuted)
f.pubMuted = pubMuted
if f.kind == webrtc.RTPCodecTypeAudio {
// for audio resync when pub muted so that sequence numbers do not jump on unmute
// audio stops forwarding during pub mute too
if pubMuted {
f.resyncLocked()
}
} else {
// Do not resync on publisher mute as forwarding can continue on unmute using same layer.
// On unmute, park current layers as streaming can continue without a key frame when publisher starts the stream.
targetLayer := f.vls.GetTarget()
if !pubMuted && targetLayer.IsValid() && f.vls.GetCurrent().Spatial == targetLayer.Spatial {
f.setupParkedLayer(targetLayer)
f.vls.SetCurrent(buffer.InvalidLayer)
}
// resync when pub muted so that sequence numbers do not jump on unmute
if pubMuted {
f.resyncLocked()
}
return true
}
@@ -476,9 +444,6 @@ func (f *Forwarder) SetMaxSpatialLayer(spatialLayer int32) (bool, buffer.VideoLa
f.logger.Debugw("setting max spatial layer", "layer", spatialLayer)
f.vls.SetMaxSpatial(spatialLayer)
f.clearParkedLayer()
return true, f.vls.GetMax()
}
@@ -497,9 +462,6 @@ func (f *Forwarder) SetMaxTemporalLayer(temporalLayer int32) (bool, buffer.Video
f.logger.Debugw("setting max temporal layer", "layer", temporalLayer)
f.vls.SetMaxTemporal(temporalLayer)
f.clearParkedLayer()
return true, f.vls.GetMax()
}
@@ -607,7 +569,6 @@ func (f *Forwarder) AllocateOptimal(availableLayers []int32, brs Bitrates, allow
maxLayer := f.vls.GetMax()
maxSeenLayer := f.vls.GetMaxSeen()
parkedLayer := f.vls.GetParked()
currentLayer := f.vls.GetCurrent()
requestSpatial := f.vls.GetRequestSpatial()
alloc := VideoAllocation{
@@ -653,14 +614,6 @@ func (f *Forwarder) AllocateOptimal(availableLayers []int32, brs Bitrates, allow
case f.pubMuted:
alloc.PauseReason = VideoPauseReasonPubMuted
// leave it at current layers for opportunistic resume
alloc.TargetLayer = currentLayer
alloc.RequestLayerSpatial = alloc.TargetLayer.Spatial
case parkedLayer.IsValid():
// if parked on a layer, let it continue
alloc.TargetLayer = parkedLayer
alloc.RequestLayerSpatial = alloc.TargetLayer.Spatial
default:
// lots of different events could end up here
@@ -754,7 +707,6 @@ func (f *Forwarder) ProvisionalAllocatePrepare(availableLayers []int32, Bitrates
Bitrates: Bitrates,
maxLayer: f.vls.GetMax(),
currentLayer: f.vls.GetCurrent(),
parkedLayer: f.vls.GetParked(),
}
f.provisional.availableLayers = make([]int32, len(availableLayers))
@@ -837,19 +789,11 @@ func (f *Forwarder) ProvisionalAllocateGetCooperativeTransition(allowOvershoot b
existingTargetLayer := f.vls.GetTarget()
if f.provisional.muted || f.provisional.pubMuted {
bandwidthRequired := int64(0)
f.provisional.allocatedLayer = buffer.InvalidLayer
if f.provisional.pubMuted {
// leave it at current for opportunistic forwarding, there is still bandwidth saving with publisher mute
f.provisional.allocatedLayer = f.provisional.currentLayer
if f.provisional.allocatedLayer.IsValid() {
bandwidthRequired = f.provisional.Bitrates[f.provisional.allocatedLayer.Spatial][f.provisional.allocatedLayer.Temporal]
}
}
return VideoTransition{
From: f.vls.GetTarget(),
To: f.provisional.allocatedLayer,
BandwidthDelta: bandwidthRequired - getBandwidthNeeded(f.provisional.Bitrates, existingTargetLayer, f.lastAllocation.BandwidthRequested),
BandwidthDelta: -getBandwidthNeeded(f.provisional.Bitrates, existingTargetLayer, f.lastAllocation.BandwidthRequested),
}
}
@@ -941,12 +885,7 @@ func (f *Forwarder) ProvisionalAllocateGetCooperativeTransition(allowOvershoot b
// if nothing available, just leave target at current to enable opportunistic forwarding in case current resumes
if !targetLayer.IsValid() {
if f.provisional.parkedLayer.IsValid() {
targetLayer = f.provisional.parkedLayer
} else {
targetLayer = f.provisional.currentLayer
}
targetLayer = f.provisional.currentLayer
if targetLayer.IsValid() {
bandwidthRequired = f.provisional.Bitrates[targetLayer.Spatial][targetLayer.Temporal]
}
@@ -981,7 +920,6 @@ func (f *Forwarder) ProvisionalAllocateGetBestWeightedTransition() VideoTransiti
targetLayer := f.vls.GetTarget()
if f.provisional.muted || f.provisional.pubMuted {
// if publisher muted, give up opportunistic resume and give back the bandwidth
f.provisional.allocatedLayer = buffer.InvalidLayer
return VideoTransition{
From: targetLayer,
@@ -1007,11 +945,7 @@ func (f *Forwarder) ProvisionalAllocateGetBestWeightedTransition() VideoTransiti
// feed has gone dry, just leave target at current to enable opportunistic forwarding in case current resumes.
// Note that this is giving back bits and opportunistic forwarding resuming might trigger congestion again,
// but that should be handled by stream allocator.
if f.provisional.parkedLayer.IsValid() {
f.provisional.allocatedLayer = f.provisional.parkedLayer
} else {
f.provisional.allocatedLayer = f.provisional.currentLayer
}
f.provisional.allocatedLayer = f.provisional.currentLayer
return VideoTransition{
From: targetLayer,
To: f.provisional.allocatedLayer,
@@ -1138,7 +1072,6 @@ func (f *Forwarder) ProvisionalAllocateCommit() VideoAllocation {
}
}
f.clearParkedLayer()
return f.updateAllocation(alloc, "cooperative")
}
@@ -1382,7 +1315,6 @@ func (f *Forwarder) Pause(availableLayers []int32, brs Bitrates) VideoAllocation
alloc.PauseReason = VideoPauseReasonBandwidth
}
f.clearParkedLayer()
return f.updateAllocation(alloc, "pause")
}
@@ -1427,31 +1359,6 @@ func (f *Forwarder) Resync() {
func (f *Forwarder) resyncLocked() {
f.vls.SetCurrent(buffer.InvalidLayer)
f.lastSSRC = 0
f.clearParkedLayer()
}
func (f *Forwarder) clearParkedLayer() {
f.vls.SetParked(buffer.InvalidLayer)
if f.parkedLayerTimer != nil {
f.parkedLayerTimer.Stop()
f.parkedLayerTimer = nil
}
}
func (f *Forwarder) setupParkedLayer(parkedLayer buffer.VideoLayer) {
f.clearParkedLayer()
f.vls.SetParked(parkedLayer)
f.parkedLayerTimer = time.AfterFunc(ParkedLayerWaitDuration, func() {
f.lock.Lock()
notify := f.vls.GetParked().IsValid()
f.clearParkedLayer()
f.lock.Unlock()
if onParkedLayerExpired := f.getOnParkedLayerExpired(); onParkedLayerExpired != nil && notify {
onParkedLayerExpired()
}
})
}
func (f *Forwarder) CheckSync() (locked bool, layer int32) {
@@ -1495,8 +1402,7 @@ func (f *Forwarder) GetTranslationParams(extPkt *buffer.ExtPacket, layer int32)
f.lock.Lock()
defer f.lock.Unlock()
// Video: Do not drop on publisher mute to enable resume on publisher unmute without a key frame.
if f.muted {
if f.muted || f.pubMuted {
return &TranslationParams{
shouldDrop: true,
}, nil
@@ -1504,13 +1410,6 @@ func (f *Forwarder) GetTranslationParams(extPkt *buffer.ExtPacket, layer int32)
switch f.kind {
case webrtc.RTPCodecTypeAudio:
// Audio: Blank frames are injected on publisher mute to ensure decoder does not get stuck at a noise frame. So, do not forward.
if f.pubMuted {
return &TranslationParams{
shouldDrop: true,
}, nil
}
return f.getTranslationParamsAudio(extPkt, layer)
case webrtc.RTPCodecTypeVideo:
return f.getTranslationParamsVideo(extPkt, layer)
@@ -1525,7 +1424,7 @@ func (f *Forwarder) processSourceSwitch(extPkt *buffer.ExtPacket, layer int32) e
f.referenceLayerSpatial = layer
f.rtpMunger.SetLastSnTs(extPkt)
f.codecMunger.SetLast(extPkt)
f.logger.Infow(
f.logger.Debugw(
"starting forwarding",
"sequenceNumber", extPkt.Packet.SequenceNumber,
"timestamp", extPkt.Packet.Timestamp,
@@ -1657,7 +1556,7 @@ func (f *Forwarder) processSourceSwitch(extPkt *buffer.ExtPacket, layer int32) e
// nominal increase
nextTS = lastTS + 1
}
f.logger.Infow(
f.logger.Debugw(
"next timestamp on switch",
"switchingAt", switchingAt.String(),
"layer", layer,
@@ -1816,7 +1715,7 @@ func (f *Forwarder) maybeStart() {
f.rtpMunger.SetLastSnTs(extPkt)
f.firstTS = extPkt.Packet.Timestamp
f.logger.Infow(
f.logger.Debugw(
"starting with dummy forwarding",
"sequenceNumber", extPkt.Packet.SequenceNumber,
"timestamp", extPkt.Packet.Timestamp,
+1 -22
View File
@@ -201,34 +201,13 @@ func TestForwarderAllocateOptimal(t *testing.T) {
f.PubMute(false)
// when parked layers valid, should stay there
f.vls.SetParked(buffer.VideoLayer{
Spatial: 0,
Temporal: 1,
})
expectedResult = VideoAllocation{
PauseReason: VideoPauseReasonFeedDry,
BandwidthRequested: 0,
BandwidthDelta: 0,
Bitrates: emptyBitrates,
TargetLayer: f.vls.GetParked(),
RequestLayerSpatial: f.vls.GetParked().Spatial,
MaxLayer: buffer.DefaultMaxLayer,
DistanceToDesired: 0,
}
result = f.AllocateOptimal(nil, emptyBitrates, true)
require.Equal(t, expectedResult, result)
require.Equal(t, expectedResult, f.lastAllocation)
require.Equal(t, f.vls.GetParked(), f.TargetLayer())
f.vls.SetParked(buffer.InvalidLayer)
// when max layers changes, target is opportunistic, but requested spatial layer should be at max
f.SetMaxTemporalLayerSeen(3)
f.vls.SetMax(buffer.VideoLayer{Spatial: 1, Temporal: 3})
expectedResult = VideoAllocation{
PauseReason: VideoPauseReasonNone,
BandwidthRequested: bitrates[1][3],
BandwidthDelta: bitrates[1][3] - bitrates[0][1],
BandwidthDelta: bitrates[1][3],
BandwidthNeeded: bitrates[1][3],
Bitrates: bitrates,
TargetLayer: buffer.DefaultMaxLayer,
+7 -7
View File
@@ -127,8 +127,8 @@ type WebRTCReceiver struct {
onStatsUpdate func(w *WebRTCReceiver, stat *livekit.AnalyticsStat)
onMaxLayerChange func(maxLayer int32)
primaryReceiver atomic.Value // *RedPrimaryReceiver
redReceiver atomic.Value // *RedReceiver
primaryReceiver atomic.Pointer[RedPrimaryReceiver]
redReceiver atomic.Pointer[RedReceiver]
redPktWriter func(pkt *buffer.ExtPacket, spatialLayer int32)
}
@@ -233,7 +233,7 @@ func NewWebRTCReceiver(
w.connectionStats.Start(w.trackInfo)
for _, ext := range receiver.GetParameters().HeaderExtensions {
if ext.URI == dd.ExtensionUrl {
if ext.URI == dd.ExtensionURI {
w.streamTrackerManager.AddDependencyDescriptorTrackers()
break
}
@@ -625,10 +625,10 @@ func (w *WebRTCReceiver) forwardRTP(layer int32) {
w.closed.Store(true)
w.closeTracks()
if pr := w.primaryReceiver.Load(); pr != nil {
pr.(*RedPrimaryReceiver).Close()
pr.Close()
}
if pr := w.redReceiver.Load(); pr != nil {
pr.(*RedReceiver).Close()
pr.Close()
}
})
@@ -732,7 +732,7 @@ func (w *WebRTCReceiver) GetPrimaryReceiverForRed() TrackReceiver {
w.bufferMu.Unlock()
}
}
return w.primaryReceiver.Load().(*RedPrimaryReceiver)
return w.primaryReceiver.Load()
}
func (w *WebRTCReceiver) GetRedReceiver() TrackReceiver {
@@ -751,7 +751,7 @@ func (w *WebRTCReceiver) GetRedReceiver() TrackReceiver {
w.bufferMu.Unlock()
}
}
return w.redReceiver.Load().(*RedReceiver)
return w.redReceiver.Load()
}
func (w *WebRTCReceiver) GetFrameRates() [][]float32 {
+56
View File
@@ -0,0 +1,56 @@
package rtpextension
import (
"encoding/binary"
"errors"
)
const (
PlayoutDelayURI = "http://www.webrtc.org/experiments/rtp-hdrext/playout-delay"
playoutDelayExtensionSize = 3
)
var (
errPlayoutDelayOverflow = errors.New("playout delay overflow")
errTooSmall = errors.New("buffer too small")
)
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | ID | len=2 | MIN delay | MAX delay |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// The wired MIN/MAX delay is in 10ms unit
type PlayOutDelay struct {
Min, Max uint16 // delay in ms
}
func PlayoutDelayFromValue(min, max uint16) PlayOutDelay {
if min >= (1<<12)*10 {
min = (1<<12 - 1) * 10
}
if max >= (1<<12)*10 {
max = (1<<12 - 1) * 10
}
return PlayOutDelay{Min: min, Max: max}
}
func (p PlayOutDelay) Marshal() ([]byte, error) {
min, max := p.Min/10, p.Max/10
if min >= 1<<12 || max >= 1<<12 {
return nil, errPlayoutDelayOverflow
}
return []byte{byte(min >> 4), byte(min<<4) | byte(max>>8), byte(max)}, nil
}
func (p *PlayOutDelay) Unmarshal(rawData []byte) error {
if len(rawData) < playoutDelayExtensionSize {
return errTooSmall
}
p.Min = (binary.BigEndian.Uint16(rawData) >> 4) * 10
p.Max = (binary.BigEndian.Uint16(rawData[1:]) & 0x0FFF) * 10
return nil
}
+35
View File
@@ -0,0 +1,35 @@
package rtpextension
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestPlayoutDelay(t *testing.T) {
p1 := PlayOutDelay{Min: 100, Max: 200}
b, err := p1.Marshal()
require.NoError(t, err)
require.Len(t, b, playoutDelayExtensionSize)
var p2 PlayOutDelay
err = p2.Unmarshal(b)
require.NoError(t, err)
require.Equal(t, p1, p2)
// overflow
p3 := PlayOutDelay{Min: 100, Max: (1 << 12) * 10}
_, err = p3.Marshal()
require.ErrorIs(t, err, errPlayoutDelayOverflow)
// too small
p4 := PlayOutDelay{}
err = p4.Unmarshal([]byte{0x00, 0x00})
require.ErrorIs(t, err, errTooSmall)
// from value
p5 := PlayoutDelayFromValue(1<<12*10, 1<<12*10+10)
_, err = p5.Marshal()
require.NoError(t, err)
require.Equal(t, uint16((1<<12)-1)*10, p5.Min)
require.Equal(t, uint16((1<<12)-1)*10, p5.Max)
}
+2 -20
View File
@@ -33,9 +33,6 @@ type Base struct {
requestSpatial int32
parkedLayer buffer.VideoLayer
previousParkedLayer buffer.VideoLayer
currentLayer buffer.VideoLayer
previousLayer buffer.VideoLayer
}
@@ -48,8 +45,6 @@ func NewBase(logger logger.Logger) *Base {
targetLayer: buffer.InvalidLayer, // start off with nothing, let streamallocator/opportunistic forwarder set the target
previousTargetLayer: buffer.InvalidLayer,
requestSpatial: buffer.InvalidLayerSpatial,
parkedLayer: buffer.InvalidLayer,
previousParkedLayer: buffer.InvalidLayer,
currentLayer: buffer.InvalidLayer,
previousLayer: buffer.InvalidLayer,
}
@@ -98,7 +93,7 @@ func (b *Base) GetRequestSpatial() int32 {
func (b *Base) CheckSync() (locked bool, layer int32) {
layer = b.GetRequestSpatial()
locked = layer == b.GetCurrent().Spatial || b.GetParked().IsValid()
locked = layer == b.GetCurrent().Spatial
return
}
@@ -118,14 +113,6 @@ func (b *Base) GetMaxSeen() buffer.VideoLayer {
return b.maxSeenLayer
}
func (b *Base) SetParked(parkedLayer buffer.VideoLayer) {
b.parkedLayer = parkedLayer
}
func (b *Base) GetParked() buffer.VideoLayer {
return b.parkedLayer
}
func (b *Base) SetCurrent(currentLayer buffer.VideoLayer) {
b.currentLayer = currentLayer
}
@@ -143,15 +130,12 @@ func (b *Base) Rollback() {
"rolling back",
"previous", b.previousLayer,
"current", b.currentLayer,
"previousParked", b.previousParkedLayer,
"parked", b.parkedLayer,
"previousTarget", b.previousTargetLayer,
"target", b.targetLayer,
"max", b.maxLayer,
"req", b.requestSpatial,
"maxSeen", b.maxSeenLayer,
)
b.parkedLayer = b.previousParkedLayer
b.currentLayer = b.previousLayer
b.targetLayer = b.previousTargetLayer
}
@@ -166,12 +150,10 @@ func (b *Base) SelectTemporal(extPkt *buffer.ExtPacket) (int32, bool) {
b.previousLayer = b.currentLayer
b.currentLayer.Temporal = next
b.logger.Infow(
b.logger.Debugw(
"updating temporal layer",
"previous", b.previousLayer,
"current", b.currentLayer,
"previousParked", b.previousParkedLayer,
"parked", b.parkedLayer,
"previousTarget", b.previousTargetLayer,
"target", b.targetLayer,
"max", b.maxLayer,
@@ -286,10 +286,6 @@ func (d *DependencyDescriptor) updateActiveDecodeTargets(activeDecodeTargetsBitm
func (d *DependencyDescriptor) CheckSync() (locked bool, layer int32) {
layer = d.GetRequestSpatial()
if d.GetParked().IsValid() {
return true, layer
}
d.decodeTargetsLock.RLock()
defer d.decodeTargetsLock.RUnlock()
for _, dt := range d.decodeTargets {
+18 -36
View File
@@ -40,22 +40,18 @@ func (s *Simulcast) IsOvershootOkay() bool {
}
func (s *Simulcast) Select(extPkt *buffer.ExtPacket, layer int32) (result VideoLayerSelectorResult) {
populateSwitches := func(isSwitching bool, isActive bool, reason string) {
if isSwitching {
result.IsSwitching = true
}
populateSwitches := func(isActive bool, reason string) {
result.IsSwitching = true
if !isActive {
result.IsResuming = true
}
if reason != "" {
s.logger.Infow(
s.logger.Debugw(
reason,
"previous", s.previousLayer,
"current", s.currentLayer,
"previousParked", s.previousParkedLayer,
"parked", s.parkedLayer,
"previousTarget", s.previousTargetLayer,
"target", s.targetLayer,
"max", s.maxLayer,
@@ -70,44 +66,30 @@ func (s *Simulcast) Select(extPkt *buffer.ExtPacket, layer int32) (result VideoL
if s.currentLayer.Spatial != s.targetLayer.Spatial {
currentLayer := s.currentLayer
// Three things to check when not locked to target
// 1. Resumable layer - don't need a key frame
// 2. Opportunistic layer upgrade - needs a key frame
// 3. Need to downgrade - needs a key frame
isSwitching := true
// Two things to check when not locked to target
// 1. Opportunistic layer upgrade - needs a key frame
// 2. Need to downgrade - needs a key frame
isActive := s.currentLayer.IsValid()
found := false
reason := ""
if s.parkedLayer.IsValid() {
if s.parkedLayer.Spatial == layer {
reason = "resuming at parked layer"
currentLayer = s.parkedLayer
isSwitching = false
if extPkt.KeyFrame {
if layer > s.currentLayer.Spatial && layer <= s.targetLayer.Spatial {
reason = "upgrading layer"
found = true
}
} else {
if extPkt.KeyFrame {
if layer > s.currentLayer.Spatial && layer <= s.targetLayer.Spatial {
reason = "upgrading layer"
found = true
}
if layer < s.currentLayer.Spatial && layer >= s.targetLayer.Spatial {
reason = "downgrading layer"
found = true
}
if layer < s.currentLayer.Spatial && layer >= s.targetLayer.Spatial {
reason = "downgrading layer"
found = true
}
if found {
currentLayer.Spatial = layer
currentLayer.Temporal = extPkt.VideoLayer.Temporal
}
if found {
currentLayer.Spatial = layer
currentLayer.Temporal = extPkt.VideoLayer.Temporal
}
}
if found {
s.previousParkedLayer = s.parkedLayer
s.parkedLayer = buffer.InvalidLayer
s.previousLayer = s.currentLayer
s.currentLayer = currentLayer
@@ -116,7 +98,7 @@ func (s *Simulcast) Select(extPkt *buffer.ExtPacket, layer int32) (result VideoL
s.targetLayer.Spatial = s.currentLayer.Spatial
}
populateSwitches(isSwitching, isActive, reason)
populateSwitches(isActive, reason)
}
}
@@ -130,7 +112,7 @@ func (s *Simulcast) Select(extPkt *buffer.ExtPacket, layer int32) (result VideoL
s.targetLayer.Spatial = layer
}
populateSwitches(true, true, "adjusting overshoot")
populateSwitches(true, "adjusting overshoot")
}
result.RTPMarker = extPkt.Packet.Marker
@@ -51,9 +51,6 @@ type VideoLayerSelector interface {
SetMaxSeenTemporal(layer int32)
GetMaxSeen() buffer.VideoLayer
SetParked(parkedLayer buffer.VideoLayer)
GetParked() buffer.VideoLayer
SetCurrent(currentLayer buffer.VideoLayer)
GetCurrent() buffer.VideoLayer