mirror of
https://github.com/livekit/livekit.git
synced 2026-07-20 02:41:12 +00:00
Merge remote-tracking branch 'origin/master' into raja_min_packets
This commit is contained in:
@@ -17,9 +17,9 @@ require (
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.4
|
||||
github.com/jxskiss/base62 v1.1.0
|
||||
github.com/livekit/mageutil v0.0.0-20230125210925-54e8a70427c1
|
||||
github.com/livekit/mediatransportutil v0.0.0-20230612070454-d5299b956135
|
||||
github.com/livekit/protocol v1.5.9
|
||||
github.com/livekit/psrpc v0.3.1
|
||||
github.com/livekit/mediatransportutil v0.0.0-20230716190407-fc4944cbc33a
|
||||
github.com/livekit/protocol v1.5.10
|
||||
github.com/livekit/psrpc v0.3.2
|
||||
github.com/mackerelio/go-osstat v0.2.4
|
||||
github.com/magefile/mage v1.15.0
|
||||
github.com/maxbrunsfeld/counterfeiter/v6 v6.6.2
|
||||
@@ -101,6 +101,6 @@ require (
|
||||
golang.org/x/text v0.10.0 // indirect
|
||||
golang.org/x/tools v0.9.3 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc // indirect
|
||||
google.golang.org/grpc v1.56.1 // indirect
|
||||
google.golang.org/grpc v1.56.2 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
)
|
||||
|
||||
@@ -122,12 +122,12 @@ github.com/lithammer/shortuuid/v4 v4.0.0 h1:QRbbVkfgNippHOS8PXDkti4NaWeyYfcBTHtw
|
||||
github.com/lithammer/shortuuid/v4 v4.0.0/go.mod h1:Zs8puNcrvf2rV9rTH51ZLLcj7ZXqQI3lv67aw4KiB1Y=
|
||||
github.com/livekit/mageutil v0.0.0-20230125210925-54e8a70427c1 h1:jm09419p0lqTkDaKb5iXdynYrzB84ErPPO4LbRASk58=
|
||||
github.com/livekit/mageutil v0.0.0-20230125210925-54e8a70427c1/go.mod h1:Rs3MhFwutWhGwmY1VQsygw28z5bWcnEYmS1OG9OxjOQ=
|
||||
github.com/livekit/mediatransportutil v0.0.0-20230612070454-d5299b956135 h1:lWYbsondvqG69czxoACDwaJ/BoyD57BahCo70ZH+m4U=
|
||||
github.com/livekit/mediatransportutil v0.0.0-20230612070454-d5299b956135/go.mod h1:MRc0zSOSzXuFt0X218SgabzlaKevkvCckPgBEoHYc34=
|
||||
github.com/livekit/protocol v1.5.9 h1:fqPOLgKkWmkmUMnpfj2KDZlidHgAazAPU2T3FewyLew=
|
||||
github.com/livekit/protocol v1.5.9/go.mod h1:GMTlFbc0JypUGo+PMilDrL45AX+yUBiQ1Cl/FGZibDY=
|
||||
github.com/livekit/psrpc v0.3.1 h1:KfylgJHvoLQcc22t/oflwMOeSnx0c14G7cWsS+9MYS4=
|
||||
github.com/livekit/psrpc v0.3.1/go.mod h1:n6JntEg+zT6Ji8InoyTpV7wusPNwGqqtxmHlkNhDN0U=
|
||||
github.com/livekit/mediatransportutil v0.0.0-20230716190407-fc4944cbc33a h1:JWpPHcMFuw0fP4swE89CfMgeUXiSN5IKvCJL/5HLI3A=
|
||||
github.com/livekit/mediatransportutil v0.0.0-20230716190407-fc4944cbc33a/go.mod h1:xirUXW8xnLGmfCwUeAv/nj1VGo1OO1BmgxrYP7jK/14=
|
||||
github.com/livekit/protocol v1.5.10 h1:lnaHMa27cbRkHybi/jvOVuRSaLsho2wCLRjKiC6ce2Y=
|
||||
github.com/livekit/protocol v1.5.10/go.mod h1:eRzojAYSPJuNgDHMlvLji/CPauj9hrgvb6rVPUj6MoU=
|
||||
github.com/livekit/psrpc v0.3.2 h1:eAaJhASme33gtoBhCRLH9jsnWcdm1tHWf0WzaDk56ew=
|
||||
github.com/livekit/psrpc v0.3.2/go.mod h1:n6JntEg+zT6Ji8InoyTpV7wusPNwGqqtxmHlkNhDN0U=
|
||||
github.com/mackerelio/go-osstat v0.2.4 h1:qxGbdPkFo65PXOb/F/nhDKpF2nGmGaCFDLXoZjJTtUs=
|
||||
github.com/mackerelio/go-osstat v0.2.4/go.mod h1:Zy+qzGdZs3A9cuIqmgbJvwbmLQH9dJvtio5ZjJTbdlQ=
|
||||
github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg=
|
||||
@@ -411,8 +411,8 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc h1:XSJ8Vk1SWuNr8S18z1NZSziL0CPIXLCCMDOEFtHBOFc=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA=
|
||||
google.golang.org/grpc v1.56.1 h1:z0dNfjIl0VpaZ9iSVjA6daGatAYwPGstTjt5vkRMFkQ=
|
||||
google.golang.org/grpc v1.56.1/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s=
|
||||
google.golang.org/grpc v1.56.2 h1:fVRFRnXvU+x6C4IlHZewvJOVHoOv1TUuQyoRsYnB4bI=
|
||||
google.golang.org/grpc v1.56.2/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
|
||||
+32
-29
@@ -370,13 +370,14 @@ func (p *ParticipantImpl) SetPermission(permission *livekit.ParticipantPermissio
|
||||
return false
|
||||
}
|
||||
|
||||
p.GetLogger().Infow("updating participant permission", "permission", permission)
|
||||
p.params.Logger.Infow("updating participant permission", "permission", permission)
|
||||
|
||||
video.UpdateFromPermission(permission)
|
||||
p.dirty.Store(true)
|
||||
|
||||
canPublish := video.GetCanPublish()
|
||||
canSubscribe := video.GetCanSubscribe()
|
||||
|
||||
onParticipantUpdate := p.onParticipantUpdate
|
||||
onClaimsChanged := p.onClaimsChanged
|
||||
|
||||
@@ -387,13 +388,7 @@ func (p *ParticipantImpl) SetPermission(permission *livekit.ParticipantPermissio
|
||||
// publish permission has been revoked then remove offending tracks
|
||||
for _, track := range p.GetPublishedTracks() {
|
||||
if !video.GetCanPublishSource(track.Source()) {
|
||||
p.RemovePublishedTrack(track, false, false)
|
||||
if p.ProtocolVersion().SupportsUnpublish() {
|
||||
p.sendTrackUnpublished(track.ID())
|
||||
} else {
|
||||
// for older clients that don't support unpublish, mute to avoid them sending data
|
||||
p.sendTrackMuted(track.ID(), true)
|
||||
}
|
||||
p.removePublishedTrack(track)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1190,22 +1185,24 @@ func (p *ParticipantImpl) updateState(state livekit.ParticipantInfo_State) {
|
||||
}
|
||||
|
||||
func (p *ParticipantImpl) setIsPublisher(isPublisher bool) {
|
||||
if p.isPublisher.Swap(isPublisher) != isPublisher {
|
||||
p.lock.Lock()
|
||||
p.requireBroadcast = true
|
||||
p.lock.Unlock()
|
||||
if p.isPublisher.Swap(isPublisher) == isPublisher {
|
||||
return
|
||||
}
|
||||
|
||||
p.dirty.Store(true)
|
||||
p.lock.Lock()
|
||||
p.requireBroadcast = true
|
||||
p.lock.Unlock()
|
||||
|
||||
// trigger update as well if participant is already fully connected
|
||||
if p.State() == livekit.ParticipantInfo_ACTIVE {
|
||||
p.lock.RLock()
|
||||
onParticipantUpdate := p.onParticipantUpdate
|
||||
p.lock.RUnlock()
|
||||
p.dirty.Store(true)
|
||||
|
||||
if onParticipantUpdate != nil {
|
||||
onParticipantUpdate(p)
|
||||
}
|
||||
// trigger update as well if participant is already fully connected
|
||||
if p.State() == livekit.ParticipantInfo_ACTIVE {
|
||||
p.lock.RLock()
|
||||
onParticipantUpdate := p.onParticipantUpdate
|
||||
p.lock.RUnlock()
|
||||
|
||||
if onParticipantUpdate != nil {
|
||||
onParticipantUpdate(p)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1220,6 +1217,16 @@ func (p *ParticipantImpl) onSubscriberOffer(offer webrtc.SessionDescription) err
|
||||
})
|
||||
}
|
||||
|
||||
func (p *ParticipantImpl) removePublishedTrack(track types.MediaTrack) {
|
||||
p.RemovePublishedTrack(track, false, false)
|
||||
if p.ProtocolVersion().SupportsUnpublish() {
|
||||
p.sendTrackUnpublished(track.ID())
|
||||
} else {
|
||||
// for older clients that don't support unpublish, mute to avoid them sending data
|
||||
p.sendTrackMuted(track.ID(), true)
|
||||
}
|
||||
}
|
||||
|
||||
// when a new remoteTrack is created, creates a Track and adds it to room
|
||||
func (p *ParticipantImpl) onMediaTrack(track *webrtc.TrackRemote, rtpReceiver *webrtc.RTPReceiver) {
|
||||
if p.IsDisconnected() {
|
||||
@@ -1242,12 +1249,12 @@ func (p *ParticipantImpl) onMediaTrack(track *webrtc.TrackRemote, rtpReceiver *w
|
||||
p.params.Logger.Warnw("no permission to publish mediaTrack", nil,
|
||||
"source", publishedTrack.Source(),
|
||||
)
|
||||
p.removePublishedTrack(publishedTrack)
|
||||
return
|
||||
}
|
||||
|
||||
if !p.IsPublisher() {
|
||||
p.setIsPublisher(true)
|
||||
}
|
||||
p.setIsPublisher(true)
|
||||
p.dirty.Store(true)
|
||||
|
||||
p.params.Logger.Infow("mediaTrack published",
|
||||
"kind", track.Kind().String(),
|
||||
@@ -1258,8 +1265,6 @@ func (p *ParticipantImpl) onMediaTrack(track *webrtc.TrackRemote, rtpReceiver *w
|
||||
"mime", track.Codec().MimeType,
|
||||
)
|
||||
|
||||
p.dirty.Store(true)
|
||||
|
||||
if !isNewTrack && !publishedTrack.HasPendingCodec() && p.IsReady() {
|
||||
p.lock.RLock()
|
||||
onTrackUpdated := p.onTrackUpdated
|
||||
@@ -1300,9 +1305,7 @@ func (p *ParticipantImpl) onDataMessage(kind livekit.DataPacket_Kind, data []byt
|
||||
p.params.Logger.Warnw("received unsupported data packet", nil, "payload", payload)
|
||||
}
|
||||
|
||||
if !p.IsPublisher() {
|
||||
p.setIsPublisher(true)
|
||||
}
|
||||
p.setIsPublisher(true)
|
||||
}
|
||||
|
||||
func (p *ParticipantImpl) onICECandidate(c *webrtc.ICECandidate, target livekit.SignalTarget) error {
|
||||
|
||||
@@ -896,6 +896,7 @@ func (r *Room) onTrackUnpublished(p types.LocalParticipant, track types.MediaTra
|
||||
}
|
||||
|
||||
func (r *Room) onParticipantUpdate(p types.LocalParticipant) {
|
||||
r.protoProxy.MarkDirty(false)
|
||||
// immediately notify when permissions or metadata changed
|
||||
r.broadcastParticipantState(p, broadcastOptions{immediate: true})
|
||||
if r.onParticipantChanged != nil {
|
||||
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
"github.com/livekit/protocol/logger"
|
||||
|
||||
"github.com/livekit/livekit-server/pkg/sfu"
|
||||
"github.com/livekit/livekit-server/pkg/sfu/buffer"
|
||||
)
|
||||
|
||||
// wrapper around WebRTC receiver, overriding its ID
|
||||
@@ -297,11 +296,11 @@ func (d *DummyReceiver) GetRedReceiver() sfu.TrackReceiver {
|
||||
return d
|
||||
}
|
||||
|
||||
func (d *DummyReceiver) GetRTCPSenderReportData(layer int32) (*buffer.RTCPSenderReportData, *buffer.RTCPSenderReportData) {
|
||||
func (d *DummyReceiver) GetCalculatedClockRate(layer int32) uint32 {
|
||||
if r, ok := d.receiver.Load().(sfu.TrackReceiver); ok {
|
||||
return r.GetRTCPSenderReportData(layer)
|
||||
return r.GetCalculatedClockRate(layer)
|
||||
}
|
||||
return nil, nil
|
||||
return 0
|
||||
}
|
||||
|
||||
func (d *DummyReceiver) GetReferenceLayerRTPTimestamp(ts uint32, layer int32, referenceLayer int32) (uint32, error) {
|
||||
|
||||
+13
-4
@@ -216,10 +216,19 @@ func (s *IngressService) ListIngress(ctx context.Context, req *livekit.ListIngre
|
||||
return nil, ErrIngressNotConnected
|
||||
}
|
||||
|
||||
infos, err := s.store.ListIngress(ctx, livekit.RoomName(req.RoomName))
|
||||
if err != nil {
|
||||
logger.Errorw("could not list ingress info", err)
|
||||
return nil, err
|
||||
var infos []*livekit.IngressInfo
|
||||
if req.IngressId != "" {
|
||||
info, err := s.store.LoadIngress(ctx, req.IngressId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
infos = []*livekit.IngressInfo{info}
|
||||
} else {
|
||||
infos, err = s.store.ListIngress(ctx, livekit.RoomName(req.RoomName))
|
||||
if err != nil {
|
||||
logger.Errorw("could not list ingress info", err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &livekit.ListIngressResponse{Items: infos}, nil
|
||||
|
||||
+19
-12
@@ -112,12 +112,6 @@ type signalService struct {
|
||||
}
|
||||
|
||||
func (r *signalService) RelaySignal(stream psrpc.ServerStream[*rpc.RelaySignalResponse, *rpc.RelaySignalRequest]) (err error) {
|
||||
// copy the context to prevent a race between the session handler closing
|
||||
// and the delivery of any parting messages from the client. take care to
|
||||
// copy the incoming rpc headers to avoid dropping any session vars.
|
||||
ctx, cancel := context.WithCancel(metadata.NewContextWithIncomingHeader(context.Background(), metadata.IncomingHeader(stream.Context())))
|
||||
defer cancel()
|
||||
|
||||
req, ok := <-stream.Channel()
|
||||
if !ok {
|
||||
return nil
|
||||
@@ -139,9 +133,6 @@ func (r *signalService) RelaySignal(stream psrpc.ServerStream[*rpc.RelaySignalRe
|
||||
"connID", ss.ConnectionId,
|
||||
)
|
||||
|
||||
reqChan := routing.NewDefaultMessageChannel(livekit.ConnectionID(ss.ConnectionId))
|
||||
defer reqChan.Close()
|
||||
|
||||
sink := routing.NewSignalMessageSink(routing.SignalSinkParams[*rpc.RelaySignalResponse, *rpc.RelaySignalRequest]{
|
||||
Logger: l,
|
||||
Stream: stream,
|
||||
@@ -149,6 +140,24 @@ func (r *signalService) RelaySignal(stream psrpc.ServerStream[*rpc.RelaySignalRe
|
||||
Writer: signalResponseMessageWriter{},
|
||||
ConnectionID: livekit.ConnectionID(ss.ConnectionId),
|
||||
})
|
||||
reqChan := routing.NewDefaultMessageChannel(livekit.ConnectionID(ss.ConnectionId))
|
||||
|
||||
go func() {
|
||||
err := routing.CopySignalStreamToMessageChannel[*rpc.RelaySignalResponse, *rpc.RelaySignalRequest](
|
||||
stream,
|
||||
reqChan,
|
||||
signalRequestMessageReader{},
|
||||
r.config,
|
||||
)
|
||||
l.Infow("signal stream closed", "error", err)
|
||||
|
||||
reqChan.Close()
|
||||
}()
|
||||
|
||||
// copy the context to prevent a race between the session handler closing
|
||||
// and the delivery of any parting messages from the client. take care to
|
||||
// copy the incoming rpc headers to avoid dropping any session vars.
|
||||
ctx := metadata.NewContextWithIncomingHeader(context.Background(), metadata.IncomingHeader(stream.Context()))
|
||||
|
||||
err = r.sessionHandler(ctx, livekit.RoomName(ss.RoomName), *pi, livekit.ConnectionID(ss.ConnectionId), reqChan, sink)
|
||||
if err != nil {
|
||||
@@ -156,9 +165,7 @@ func (r *signalService) RelaySignal(stream psrpc.ServerStream[*rpc.RelaySignalRe
|
||||
return
|
||||
}
|
||||
|
||||
err = routing.CopySignalStreamToMessageChannel[*rpc.RelaySignalResponse, *rpc.RelaySignalRequest](stream, reqChan, signalRequestMessageReader{}, r.config)
|
||||
l.Infow("signal stream closed", "error", err)
|
||||
|
||||
stream.Hijack()
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
+93
-46
@@ -750,13 +750,13 @@ func (r *RTPStats) GetRtt() uint32 {
|
||||
}
|
||||
|
||||
func (r *RTPStats) SetRtcpSenderReportData(srData *RTCPSenderReportData) {
|
||||
if srData == nil {
|
||||
return
|
||||
}
|
||||
|
||||
r.lock.Lock()
|
||||
defer r.lock.Unlock()
|
||||
|
||||
if srData == nil || !r.initialized {
|
||||
return
|
||||
}
|
||||
|
||||
// prevent against extreme case of anachronous sender reports
|
||||
if r.srNewest != nil && r.srNewest.NTPTimestamp > srData.NTPTimestamp {
|
||||
r.logger.Infow(
|
||||
@@ -767,29 +767,6 @@ func (r *RTPStats) SetRtcpSenderReportData(srData *RTCPSenderReportData) {
|
||||
return
|
||||
}
|
||||
|
||||
// monitor and log RTP timestamp anomalies
|
||||
var ntpDiffSinceLast time.Duration
|
||||
var rtpDiffSinceLast uint32
|
||||
var arrivalDiffSinceLast time.Duration
|
||||
var expectedTimeDiffSinceLast float64
|
||||
var reason string
|
||||
if r.srNewest != nil {
|
||||
ntpDiffSinceLast = srData.NTPTimestamp.Time().Sub(r.srNewest.NTPTimestamp.Time())
|
||||
rtpDiffSinceLast = srData.RTPTimestamp - r.srNewest.RTPTimestamp
|
||||
arrivalDiffSinceLast = srData.At.Sub(r.srNewest.At)
|
||||
|
||||
expectedTimeDiffSinceLast = float64(rtpDiffSinceLast) / float64(r.params.ClockRate)
|
||||
|
||||
if (srData.RTPTimestamp - r.srNewest.RTPTimestamp) > (1 << 31) {
|
||||
reason = "received sender report, out-of-order" // should not happen, just a sanity check
|
||||
} else {
|
||||
if math.Abs(expectedTimeDiffSinceLast-ntpDiffSinceLast.Seconds()) > 0.2 {
|
||||
// more than 200 ms away from expected delta
|
||||
reason = "received sender report, time warp"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cycles := uint64(0)
|
||||
if r.srNewest != nil {
|
||||
cycles = r.srNewest.RTPTimestampExt & 0xFF_FF_FF_FF_00_00_00_00
|
||||
@@ -800,15 +777,49 @@ func (r *RTPStats) SetRtcpSenderReportData(srData *RTCPSenderReportData) {
|
||||
|
||||
srDataCopy := *srData
|
||||
srDataCopy.RTPTimestampExt = uint64(srDataCopy.RTPTimestamp) + cycles
|
||||
|
||||
// monitor and log RTP timestamp anomalies
|
||||
var ntpDiffSinceLast time.Duration
|
||||
var rtpDiffSinceLast uint32
|
||||
var arrivalDiffSinceLast time.Duration
|
||||
var expectedTimeDiffSinceLast float64
|
||||
var isWarped bool
|
||||
if r.srNewest != nil {
|
||||
if srDataCopy.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.
|
||||
// Under such a condition reset the sender reports to start from this point.
|
||||
// Resetting will ensure sample rate calculations do not go haywire due to negative time.
|
||||
r.logger.Infow(
|
||||
"received sender report, out-of-order, resetting",
|
||||
"prevTSExt", r.srNewest.RTPTimestampExt,
|
||||
"prevNTP", r.srNewest.NTPTimestamp.Time().String(),
|
||||
"currTSExt", srDataCopy.RTPTimestampExt,
|
||||
"currNTP", srDataCopy.NTPTimestamp.Time().String(),
|
||||
)
|
||||
r.srFirst = &srDataCopy
|
||||
r.srNewest = &srDataCopy
|
||||
}
|
||||
|
||||
ntpDiffSinceLast = srDataCopy.NTPTimestamp.Time().Sub(r.srNewest.NTPTimestamp.Time())
|
||||
rtpDiffSinceLast = srDataCopy.RTPTimestamp - r.srNewest.RTPTimestamp
|
||||
arrivalDiffSinceLast = srDataCopy.At.Sub(r.srNewest.At)
|
||||
expectedTimeDiffSinceLast = float64(rtpDiffSinceLast) / float64(r.params.ClockRate)
|
||||
if math.Abs(expectedTimeDiffSinceLast-ntpDiffSinceLast.Seconds()) > 0.2 {
|
||||
// more than 200 ms away from expected delta
|
||||
isWarped = true
|
||||
}
|
||||
}
|
||||
|
||||
r.srNewest = &srDataCopy
|
||||
if r.srFirst == nil {
|
||||
r.srFirst = &srDataCopy
|
||||
}
|
||||
|
||||
if reason != "" {
|
||||
if isWarped {
|
||||
packetDriftResult, reportDriftResult := r.getDrift()
|
||||
r.logger.Infow(
|
||||
reason,
|
||||
"received sender report, time warp",
|
||||
"ntp", srData.NTPTimestamp.Time().String(),
|
||||
"rtp", srData.RTPTimestamp,
|
||||
"arrival", srData.At.String(),
|
||||
@@ -840,26 +851,22 @@ func (r *RTPStats) GetRtcpSenderReportData() (srFirst *RTCPSenderReportData, srN
|
||||
return
|
||||
}
|
||||
|
||||
func (r *RTPStats) GetExpectedRTPTimestamp(at time.Time) (uint32, uint64, error) {
|
||||
func (r *RTPStats) GetExpectedRTPTimestamp(at time.Time) (expectedTSExt uint64, err error) {
|
||||
r.lock.RLock()
|
||||
defer r.lock.RUnlock()
|
||||
|
||||
if !r.initialized {
|
||||
return 0, 0, errors.New("uninitilaized")
|
||||
err = errors.New("uninitilaized")
|
||||
return
|
||||
}
|
||||
|
||||
timeDiff := at.Sub(r.firstTime)
|
||||
expectedRTPDiff := timeDiff.Nanoseconds() * int64(r.params.ClockRate) / 1e9
|
||||
expectedExtRTP := r.extStartTS + uint64(expectedRTPDiff)
|
||||
|
||||
minTS := ^uint64(0)
|
||||
if r.srNewest != nil {
|
||||
minTS = r.srNewest.RTPTimestampExt
|
||||
}
|
||||
return uint32(expectedExtRTP), minTS, nil
|
||||
expectedTSExt = r.extStartTS + uint64(expectedRTPDiff)
|
||||
return
|
||||
}
|
||||
|
||||
func (r *RTPStats) GetRtcpSenderReport(ssrc uint32, srFirst *RTCPSenderReportData, srNewest *RTCPSenderReportData) *rtcp.SenderReport {
|
||||
func (r *RTPStats) GetRtcpSenderReport(ssrc uint32, calculatedClockRate uint32) *rtcp.SenderReport {
|
||||
r.lock.Lock()
|
||||
defer r.lock.Unlock()
|
||||
|
||||
@@ -877,24 +884,42 @@ func (r *RTPStats) GetRtcpSenderReport(ssrc uint32, srFirst *RTCPSenderReportDat
|
||||
|
||||
// It is possible that publisher is pacing at a slower rate.
|
||||
// That would make `highestTS` to be lagging the RTP time stamp in the RTCP Sender Report from publisher.
|
||||
// Check for that and use the later time stamp if applicable.
|
||||
// Check for that using calculated clock rate and use the later time stamp if applicable.
|
||||
tsCycles := r.tsCycles
|
||||
if nowRTP < r.highestTS {
|
||||
tsCycles++
|
||||
}
|
||||
nowRTPExt := getExtTS(nowRTP, tsCycles)
|
||||
if srFirst != nil && srNewest != nil && srFirst.RTPTimestamp != srNewest.RTPTimestamp {
|
||||
// use incoming rate as a guide
|
||||
tsf := srNewest.NTPTimestamp.Time().Sub(srFirst.NTPTimestamp.Time())
|
||||
rdsf := srNewest.RTPTimestampExt - srFirst.RTPTimestampExt
|
||||
sr := float64(rdsf) / tsf.Seconds()
|
||||
nowRTPExtUsingRate := r.extStartTS + uint64(sr*timeSinceFirst.Seconds())
|
||||
var nowRTPExtUsingRate uint64
|
||||
if calculatedClockRate != 0 {
|
||||
nowRTPExtUsingRate = r.extStartTS + uint64(float64(calculatedClockRate)*timeSinceFirst.Seconds())
|
||||
if nowRTPExtUsingRate > nowRTPExt {
|
||||
nowRTPExt = nowRTPExtUsingRate
|
||||
nowRTP = uint32(nowRTPExtUsingRate)
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
// Current report could be behind due to the following
|
||||
// - Publisher pacing
|
||||
// - Due to above, report from publisher side is ahead of packet timestamps.
|
||||
// Note that report will map wall clock to timestamp at capture time and happens before the pacer.
|
||||
// - Pause/Mute followed by resume, some combination of events that could
|
||||
// result in this module not having calculated clock rate of publisher side.
|
||||
// - When the above happens, current will be generated using highestTS which could be behind.
|
||||
// That could end up behind the last report's timestamp in extreme cases
|
||||
r.logger.Infow(
|
||||
"sending sender report, out-of-order, repairing",
|
||||
"prevTSExt", r.srNewest.RTPTimestampExt,
|
||||
"prevNTP", r.srNewest.NTPTimestamp.Time().String(),
|
||||
"currTSExt", nowRTPExt,
|
||||
"currNTP", nowNTP.Time().String(),
|
||||
)
|
||||
ntpDiffSinceLast := nowNTP.Time().Sub(r.srNewest.NTPTimestamp.Time())
|
||||
nowRTPExt = r.srNewest.RTPTimestampExt + uint64(ntpDiffSinceLast.Seconds()*float64(r.params.ClockRate))
|
||||
}
|
||||
|
||||
// monitor and log RTP timestamp anomalies
|
||||
var ntpDiffSinceLast time.Duration
|
||||
var rtpDiffSinceLast uint32
|
||||
@@ -938,6 +963,28 @@ func (r *RTPStats) GetRtcpSenderReport(ssrc uint32, srFirst *RTCPSenderReportDat
|
||||
"reportDrift", reportDriftResult.String(),
|
||||
"highestTS", r.highestTS,
|
||||
"highestTime", r.highestTime.String(),
|
||||
"calculatedClockRate", calculatedClockRate,
|
||||
"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,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
+130
-71
@@ -42,6 +42,8 @@ type TrackSender interface {
|
||||
HandleRTCPSenderReportData(payloadType webrtc.PayloadType, layer int32, srData *buffer.RTCPSenderReportData) error
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
const (
|
||||
RTPPaddingMaxPayloadSize = 255
|
||||
RTPPaddingEstimatedHeaderSize = 20
|
||||
@@ -60,6 +62,8 @@ const (
|
||||
maxPaddingOnMuteDuration = 5 * time.Second
|
||||
)
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
var (
|
||||
ErrUnknownKind = errors.New("unknown kind of codec")
|
||||
ErrOutOfOrderSequenceNumberCacheMiss = errors.New("out-of-order sequence number not found in cache")
|
||||
@@ -197,14 +201,13 @@ type DownTrack struct {
|
||||
transceiver *webrtc.RTPTransceiver
|
||||
writeStream webrtc.TrackLocalWriter
|
||||
rtcpReader *buffer.RTCPReader
|
||||
onCloseHandler func(willBeResumed bool)
|
||||
onBinding func(error)
|
||||
|
||||
listenerLock sync.RWMutex
|
||||
receiverReportListeners []ReceiverReportListener
|
||||
|
||||
bindLock sync.Mutex
|
||||
bound atomic.Bool
|
||||
bindLock sync.Mutex
|
||||
bound atomic.Bool
|
||||
onBinding func(error)
|
||||
|
||||
isClosed atomic.Bool
|
||||
connected atomic.Bool
|
||||
@@ -235,14 +238,13 @@ type DownTrack struct {
|
||||
|
||||
pacer pacer.Pacer
|
||||
|
||||
// update stats
|
||||
onStatsUpdate func(dt *DownTrack, stat *livekit.AnalyticsStat)
|
||||
maxLayerNotifierCh chan struct{}
|
||||
|
||||
// when max subscribed layer changes
|
||||
cbMu sync.RWMutex
|
||||
onStatsUpdate func(dt *DownTrack, stat *livekit.AnalyticsStat)
|
||||
onMaxSubscribedLayerChanged func(dt *DownTrack, layer int32)
|
||||
|
||||
// update rtt
|
||||
onRttUpdate func(dt *DownTrack, rtt uint32)
|
||||
onRttUpdate func(dt *DownTrack, rtt uint32)
|
||||
onCloseHandler func(willBeResumed bool)
|
||||
}
|
||||
|
||||
// NewDownTrack returns a DownTrack.
|
||||
@@ -266,17 +268,18 @@ func NewDownTrack(
|
||||
}
|
||||
|
||||
d := &DownTrack{
|
||||
logger: logger,
|
||||
id: r.TrackID(),
|
||||
subscriberID: subID,
|
||||
maxTrack: mt,
|
||||
streamID: r.StreamID(),
|
||||
bufferFactory: bf,
|
||||
receiver: r,
|
||||
upstreamCodecs: codecs,
|
||||
kind: kind,
|
||||
codec: codecs[0].RTPCodecCapability,
|
||||
pacer: pacer,
|
||||
logger: logger,
|
||||
id: r.TrackID(),
|
||||
subscriberID: subID,
|
||||
maxTrack: mt,
|
||||
streamID: r.StreamID(),
|
||||
bufferFactory: bf,
|
||||
receiver: r,
|
||||
upstreamCodecs: codecs,
|
||||
kind: kind,
|
||||
codec: codecs[0].RTPCodecCapability,
|
||||
pacer: pacer,
|
||||
maxLayerNotifierCh: make(chan struct{}, 20),
|
||||
}
|
||||
d.forwarder = NewForwarder(
|
||||
d.kind,
|
||||
@@ -307,11 +310,15 @@ func NewDownTrack(
|
||||
Logger: d.logger.WithValues("direction", "down"),
|
||||
})
|
||||
d.connectionStats.OnStatsUpdate(func(_cs *connectionquality.ConnectionStats, stat *livekit.AnalyticsStat) {
|
||||
if d.onStatsUpdate != nil {
|
||||
d.onStatsUpdate(d, stat)
|
||||
if onStatsUpdate := d.getOnStatsUpdate(); onStatsUpdate != nil {
|
||||
onStatsUpdate(d, stat)
|
||||
}
|
||||
})
|
||||
|
||||
if d.kind == webrtc.RTPCodecTypeVideo {
|
||||
go d.maxLayerNotifierWorker()
|
||||
}
|
||||
|
||||
return d, nil
|
||||
}
|
||||
|
||||
@@ -541,6 +548,7 @@ func (d *DownTrack) keyFrameRequester(generation uint32, layer int32) {
|
||||
if d.IsClosed() || layer == buffer.InvalidLayerSpatial {
|
||||
return
|
||||
}
|
||||
|
||||
interval := 2 * d.rtpStats.GetRtt()
|
||||
if interval < keyFrameIntervalMin {
|
||||
interval = keyFrameIntervalMin
|
||||
@@ -550,7 +558,13 @@ func (d *DownTrack) keyFrameRequester(generation uint32, layer int32) {
|
||||
}
|
||||
ticker := time.NewTicker(time.Duration(interval) * time.Millisecond)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
locked, _ := d.forwarder.CheckSync()
|
||||
if locked {
|
||||
return
|
||||
}
|
||||
|
||||
if d.connected.Load() {
|
||||
d.logger.Debugw("sending PLI for layer lock", "generation", generation, "layer", layer)
|
||||
d.receiver.SendPLI(layer, false)
|
||||
@@ -565,6 +579,34 @@ func (d *DownTrack) keyFrameRequester(generation uint32, layer int32) {
|
||||
}
|
||||
}
|
||||
|
||||
func (d *DownTrack) postMaxLayerNotifierEvent() {
|
||||
if d.IsClosed() {
|
||||
return
|
||||
}
|
||||
|
||||
select {
|
||||
case d.maxLayerNotifierCh <- struct{}{}:
|
||||
default:
|
||||
d.logger.Warnw("max layer notifier event queue full", nil)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *DownTrack) maxLayerNotifierWorker() {
|
||||
more := true
|
||||
for more {
|
||||
_, more = <-d.maxLayerNotifierCh
|
||||
|
||||
maxLayerSpatial := buffer.InvalidLayerSpatial
|
||||
if more {
|
||||
maxLayerSpatial = d.forwarder.GetMaxSubscribedSpatial()
|
||||
}
|
||||
if onMaxSubscribedLayerChanged := d.getOnMaxLayerChanged(); onMaxSubscribedLayerChanged != nil {
|
||||
d.logger.Infow("max subscribed layer changed", "maxLayerSpatial", maxLayerSpatial)
|
||||
onMaxSubscribedLayerChanged(d, maxLayerSpatial)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WriteRTP writes an RTP Packet to the DownTrack
|
||||
func (d *DownTrack) WriteRTP(extPkt *buffer.ExtPacket, layer int32) error {
|
||||
if !d.bound.Load() || !d.connected.Load() {
|
||||
@@ -725,17 +767,17 @@ func (d *DownTrack) WritePaddingRTP(bytesToSend int, paddingOnMute bool, forceMa
|
||||
|
||||
// Mute enables or disables media forwarding - subscriber triggered
|
||||
func (d *DownTrack) Mute(muted bool) {
|
||||
changed, maxLayer := d.forwarder.Mute(muted)
|
||||
d.handleMute(muted, false, changed, maxLayer)
|
||||
changed := d.forwarder.Mute(muted)
|
||||
d.handleMute(muted, changed)
|
||||
}
|
||||
|
||||
// PubMute enables or disables media forwarding - publisher side
|
||||
func (d *DownTrack) PubMute(pubMuted bool) {
|
||||
changed, maxLayer := d.forwarder.PubMute(pubMuted)
|
||||
d.handleMute(pubMuted, true, changed, maxLayer)
|
||||
changed := d.forwarder.PubMute(pubMuted)
|
||||
d.handleMute(pubMuted, changed)
|
||||
}
|
||||
|
||||
func (d *DownTrack) handleMute(muted bool, isPub bool, changed bool, maxLayer buffer.VideoLayer) {
|
||||
func (d *DownTrack) handleMute(muted bool, changed bool) {
|
||||
if !changed {
|
||||
return
|
||||
}
|
||||
@@ -762,18 +804,7 @@ func (d *DownTrack) handleMute(muted bool, isPub bool, changed bool, maxLayer bu
|
||||
// Note that while publisher mute is active, subscriber changes can also happen
|
||||
// and that could turn on/off layers on publisher side.
|
||||
//
|
||||
if !isPub && d.onMaxSubscribedLayerChanged != nil && d.kind == webrtc.RTPCodecTypeVideo {
|
||||
notifyLayer := buffer.InvalidLayerSpatial
|
||||
if !muted {
|
||||
//
|
||||
// When unmuting, don't wait for layer lock as
|
||||
// client might need to be notified to start layers
|
||||
// before locking can happen in the forwarder.
|
||||
//
|
||||
notifyLayer = maxLayer.Spatial
|
||||
}
|
||||
d.onMaxSubscribedLayerChanged(d, notifyLayer)
|
||||
}
|
||||
d.postMaxLayerNotifierEvent()
|
||||
|
||||
if sal := d.getStreamAllocatorListener(); sal != nil {
|
||||
sal.OnSubscriptionChanged(d)
|
||||
@@ -856,12 +887,10 @@ func (d *DownTrack) CloseWithFlush(flush bool) {
|
||||
d.rtpStats.Stop()
|
||||
d.logger.Infow("rtp stats", "direction", "downstream", "mime", d.mime, "ssrc", d.ssrc, "stats", d.rtpStats.ToString())
|
||||
|
||||
if d.onMaxSubscribedLayerChanged != nil && d.kind == webrtc.RTPCodecTypeVideo {
|
||||
d.onMaxSubscribedLayerChanged(d, buffer.InvalidLayerSpatial)
|
||||
}
|
||||
close(d.maxLayerNotifierCh)
|
||||
|
||||
if d.onCloseHandler != nil {
|
||||
d.onCloseHandler(!flush)
|
||||
if onCloseHandler := d.getOnCloseHandler(); onCloseHandler != nil {
|
||||
onCloseHandler(!flush)
|
||||
}
|
||||
|
||||
d.stopKeyFrameRequester()
|
||||
@@ -869,21 +898,12 @@ func (d *DownTrack) CloseWithFlush(flush bool) {
|
||||
}
|
||||
|
||||
func (d *DownTrack) SetMaxSpatialLayer(spatialLayer int32) {
|
||||
changed, maxLayer, currentLayer := d.forwarder.SetMaxSpatialLayer(spatialLayer)
|
||||
changed, maxLayer := d.forwarder.SetMaxSpatialLayer(spatialLayer)
|
||||
if !changed {
|
||||
return
|
||||
}
|
||||
|
||||
if d.onMaxSubscribedLayerChanged != nil && d.kind == webrtc.RTPCodecTypeVideo && maxLayer.SpatialGreaterThanOrEqual(currentLayer) {
|
||||
//
|
||||
// Notify when new max is
|
||||
// 1. Equal to current -> already locked to the new max
|
||||
// 2. Greater than current -> two scenarios
|
||||
// a. is higher than previous max -> client may need to start higher layer before forwarder can lock
|
||||
// b. is lower than previous max -> client can stop higher layer(s)
|
||||
//
|
||||
d.onMaxSubscribedLayerChanged(d, maxLayer.Spatial)
|
||||
}
|
||||
d.postMaxLayerNotifierEvent()
|
||||
|
||||
if sal := d.getStreamAllocatorListener(); sal != nil {
|
||||
sal.OnSubscribedLayerChanged(d, maxLayer)
|
||||
@@ -891,7 +911,7 @@ func (d *DownTrack) SetMaxSpatialLayer(spatialLayer int32) {
|
||||
}
|
||||
|
||||
func (d *DownTrack) SetMaxTemporalLayer(temporalLayer int32) {
|
||||
changed, maxLayer, _ := d.forwarder.SetMaxTemporalLayer(temporalLayer)
|
||||
changed, maxLayer := d.forwarder.SetMaxTemporalLayer(temporalLayer)
|
||||
if !changed {
|
||||
return
|
||||
}
|
||||
@@ -973,10 +993,23 @@ func (d *DownTrack) UpTrackBitrateReport(availableLayers []int32, bitrates Bitra
|
||||
|
||||
// OnCloseHandler method to be called on remote tracked removed
|
||||
func (d *DownTrack) OnCloseHandler(fn func(willBeResumed bool)) {
|
||||
d.cbMu.Lock()
|
||||
defer d.cbMu.Unlock()
|
||||
|
||||
d.onCloseHandler = fn
|
||||
}
|
||||
|
||||
func (d *DownTrack) getOnCloseHandler() func(willBeResumed bool) {
|
||||
d.cbMu.RLock()
|
||||
defer d.cbMu.RUnlock()
|
||||
|
||||
return d.onCloseHandler
|
||||
}
|
||||
|
||||
func (d *DownTrack) OnBinding(fn func(error)) {
|
||||
d.bindLock.Lock()
|
||||
defer d.bindLock.Unlock()
|
||||
|
||||
d.onBinding = fn
|
||||
}
|
||||
|
||||
@@ -988,17 +1021,47 @@ func (d *DownTrack) AddReceiverReportListener(listener ReceiverReportListener) {
|
||||
}
|
||||
|
||||
func (d *DownTrack) OnStatsUpdate(fn func(dt *DownTrack, stat *livekit.AnalyticsStat)) {
|
||||
d.cbMu.Lock()
|
||||
defer d.cbMu.Unlock()
|
||||
|
||||
d.onStatsUpdate = fn
|
||||
}
|
||||
|
||||
func (d *DownTrack) getOnStatsUpdate() func(dt *DownTrack, stat *livekit.AnalyticsStat) {
|
||||
d.cbMu.RLock()
|
||||
defer d.cbMu.RUnlock()
|
||||
|
||||
return d.onStatsUpdate
|
||||
}
|
||||
|
||||
func (d *DownTrack) OnRttUpdate(fn func(dt *DownTrack, rtt uint32)) {
|
||||
d.cbMu.Lock()
|
||||
defer d.cbMu.Unlock()
|
||||
|
||||
d.onRttUpdate = fn
|
||||
}
|
||||
|
||||
func (d *DownTrack) getOnRttUpdate() func(dt *DownTrack, rtt uint32) {
|
||||
d.cbMu.RLock()
|
||||
defer d.cbMu.RUnlock()
|
||||
|
||||
return d.onRttUpdate
|
||||
}
|
||||
|
||||
func (d *DownTrack) OnMaxLayerChanged(fn func(dt *DownTrack, layer int32)) {
|
||||
d.cbMu.Lock()
|
||||
defer d.cbMu.Unlock()
|
||||
|
||||
d.onMaxSubscribedLayerChanged = fn
|
||||
}
|
||||
|
||||
func (d *DownTrack) getOnMaxLayerChanged() func(dt *DownTrack, layer int32) {
|
||||
d.cbMu.RLock()
|
||||
defer d.cbMu.RUnlock()
|
||||
|
||||
return d.onMaxSubscribedLayerChanged
|
||||
}
|
||||
|
||||
func (d *DownTrack) IsDeficient() bool {
|
||||
return d.forwarder.IsDeficient()
|
||||
}
|
||||
@@ -1102,8 +1165,11 @@ func (d *DownTrack) CreateSenderReport() *rtcp.SenderReport {
|
||||
return nil
|
||||
}
|
||||
|
||||
srFirst, srNewest := d.receiver.GetRTCPSenderReportData(d.forwarder.GetReferenceLayerSpatial())
|
||||
return d.rtpStats.GetRtcpSenderReport(d.ssrc, srFirst, srNewest)
|
||||
clockLayer := d.forwarder.CurrentLayer().Spatial
|
||||
if clockLayer == buffer.InvalidLayerSpatial {
|
||||
clockLayer = d.forwarder.GetReferenceLayerSpatial()
|
||||
}
|
||||
return d.rtpStats.GetRtcpSenderReport(d.ssrc, d.receiver.GetCalculatedClockRate(clockLayer))
|
||||
}
|
||||
|
||||
func (d *DownTrack) writeBlankFrameRTP(duration float32, generation uint32) chan struct{} {
|
||||
@@ -1356,8 +1422,8 @@ func (d *DownTrack) handleRTCP(bytes []byte) {
|
||||
d.sequencer.setRTT(rttToReport)
|
||||
}
|
||||
|
||||
if d.onRttUpdate != nil {
|
||||
d.onRttUpdate(d, rttToReport)
|
||||
if onRttUpdate := d.getOnRttUpdate(); onRttUpdate != nil {
|
||||
onRttUpdate(d, rttToReport)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1542,7 +1608,7 @@ func (d *DownTrack) DebugInfo() map[string]interface{} {
|
||||
}
|
||||
}
|
||||
|
||||
func (d *DownTrack) getExpectedRTPTimestamp(at time.Time) (uint32, uint64, error) {
|
||||
func (d *DownTrack) getExpectedRTPTimestamp(at time.Time) (uint64, error) {
|
||||
return d.rtpStats.GetExpectedRTPTimestamp(at)
|
||||
}
|
||||
|
||||
@@ -1745,15 +1811,8 @@ func (d *DownTrack) packetSent(md interface{}, hdr *rtp.Header, payloadSize int,
|
||||
}
|
||||
|
||||
if spmd.tp != nil {
|
||||
if spmd.tp.isSwitchingToMaxSpatial && d.onMaxSubscribedLayerChanged != nil && d.kind == webrtc.RTPCodecTypeVideo {
|
||||
d.onMaxSubscribedLayerChanged(d, spmd.tp.maxSpatialLayer)
|
||||
}
|
||||
|
||||
if spmd.tp.isSwitchingToRequestSpatial {
|
||||
locked, _ := d.forwarder.CheckSync()
|
||||
if locked {
|
||||
d.stopKeyFrameRequester()
|
||||
}
|
||||
if spmd.tp.isSwitching {
|
||||
d.postMaxLayerNotifierEvent()
|
||||
}
|
||||
|
||||
if spmd.tp.isResuming {
|
||||
|
||||
+231
-166
@@ -26,6 +26,10 @@ const (
|
||||
FlagFilterRTX = true
|
||||
TransitionCostSpatial = 10
|
||||
ParkedLayerWaitDuration = 2 * time.Second
|
||||
|
||||
ResumeBehindThresholdSeconds = float64(0.1) // 100ms
|
||||
LayerSwitchBehindThresholdSeconds = float64(0.05) // 50ms
|
||||
SwitchAheadThresholdSeconds = float64(0.025) // 25ms
|
||||
)
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
@@ -125,15 +129,13 @@ func (v VideoTransition) String() string {
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
type TranslationParams struct {
|
||||
shouldDrop bool
|
||||
isResuming bool
|
||||
isSwitchingToRequestSpatial bool
|
||||
isSwitchingToMaxSpatial bool
|
||||
maxSpatialLayer int32
|
||||
rtp *TranslationParamsRTP
|
||||
codecBytes []byte
|
||||
ddBytes []byte
|
||||
marker bool
|
||||
shouldDrop bool
|
||||
isResuming bool
|
||||
isSwitching bool
|
||||
rtp *TranslationParamsRTP
|
||||
codecBytes []byte
|
||||
ddBytes []byte
|
||||
marker bool
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
@@ -171,7 +173,7 @@ type Forwarder struct {
|
||||
kind webrtc.RTPCodecType
|
||||
logger logger.Logger
|
||||
getReferenceLayerRTPTimestamp func(ts uint32, layer int32, referenceLayer int32) (uint32, error)
|
||||
getExpectedRTPTimestamp func(at time.Time) (uint32, uint64, error)
|
||||
getExpectedRTPTimestamp func(at time.Time) (uint64, error)
|
||||
|
||||
muted bool
|
||||
pubMuted bool
|
||||
@@ -202,7 +204,7 @@ func NewForwarder(
|
||||
kind webrtc.RTPCodecType,
|
||||
logger logger.Logger,
|
||||
getReferenceLayerRTPTimestamp func(ts uint32, layer int32, referenceLayer int32) (uint32, error),
|
||||
getExpectedRTPTimestamp func(at time.Time) (uint32, uint64, error),
|
||||
getExpectedRTPTimestamp func(at time.Time) (uint64, error),
|
||||
) *Forwarder {
|
||||
f := &Forwarder{
|
||||
kind: kind,
|
||||
@@ -356,12 +358,12 @@ func (f *Forwarder) SeedState(state ForwarderState) {
|
||||
f.refTSOffset = state.RefTSOffset
|
||||
}
|
||||
|
||||
func (f *Forwarder) Mute(muted bool) (bool, buffer.VideoLayer) {
|
||||
func (f *Forwarder) Mute(muted bool) bool {
|
||||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
|
||||
if f.muted == muted {
|
||||
return false, f.vls.GetMax()
|
||||
return false
|
||||
}
|
||||
|
||||
// Do not mute when paused due to bandwidth limitation.
|
||||
@@ -380,7 +382,7 @@ func (f *Forwarder) Mute(muted bool) (bool, buffer.VideoLayer) {
|
||||
// the case of intentional mute.
|
||||
if muted && f.isDeficientLocked() && f.lastAllocation.PauseReason == VideoPauseReasonBandwidth {
|
||||
f.logger.Infow("ignoring forwarder mute, paused due to congestion")
|
||||
return false, f.vls.GetMax()
|
||||
return false
|
||||
}
|
||||
|
||||
f.logger.Debugw("setting forwarder mute", "muted", muted)
|
||||
@@ -391,7 +393,7 @@ func (f *Forwarder) Mute(muted bool) (bool, buffer.VideoLayer) {
|
||||
f.resyncLocked()
|
||||
}
|
||||
|
||||
return true, f.vls.GetMax()
|
||||
return true
|
||||
}
|
||||
|
||||
func (f *Forwarder) IsMuted() bool {
|
||||
@@ -401,12 +403,12 @@ func (f *Forwarder) IsMuted() bool {
|
||||
return f.muted
|
||||
}
|
||||
|
||||
func (f *Forwarder) PubMute(pubMuted bool) (bool, buffer.VideoLayer) {
|
||||
func (f *Forwarder) PubMute(pubMuted bool) bool {
|
||||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
|
||||
if f.pubMuted == pubMuted {
|
||||
return false, f.vls.GetMax()
|
||||
return false
|
||||
}
|
||||
|
||||
f.logger.Debugw("setting forwarder pub mute", "pubMuted", pubMuted)
|
||||
@@ -428,7 +430,7 @@ func (f *Forwarder) PubMute(pubMuted bool) (bool, buffer.VideoLayer) {
|
||||
}
|
||||
}
|
||||
|
||||
return true, f.vls.GetMax()
|
||||
return true
|
||||
}
|
||||
|
||||
func (f *Forwarder) IsPubMuted() bool {
|
||||
@@ -445,17 +447,17 @@ func (f *Forwarder) IsAnyMuted() bool {
|
||||
return f.muted || f.pubMuted
|
||||
}
|
||||
|
||||
func (f *Forwarder) SetMaxSpatialLayer(spatialLayer int32) (bool, buffer.VideoLayer, buffer.VideoLayer) {
|
||||
func (f *Forwarder) SetMaxSpatialLayer(spatialLayer int32) (bool, buffer.VideoLayer) {
|
||||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
|
||||
if f.kind == webrtc.RTPCodecTypeAudio {
|
||||
return false, buffer.InvalidLayer, buffer.InvalidLayer
|
||||
return false, buffer.InvalidLayer
|
||||
}
|
||||
|
||||
existingMax := f.vls.GetMax()
|
||||
if spatialLayer == existingMax.Spatial {
|
||||
return false, existingMax, f.vls.GetCurrent()
|
||||
return false, existingMax
|
||||
}
|
||||
|
||||
f.logger.Debugw("setting max spatial layer", "layer", spatialLayer)
|
||||
@@ -463,20 +465,20 @@ func (f *Forwarder) SetMaxSpatialLayer(spatialLayer int32) (bool, buffer.VideoLa
|
||||
|
||||
f.clearParkedLayer()
|
||||
|
||||
return true, f.vls.GetMax(), f.vls.GetCurrent()
|
||||
return true, f.vls.GetMax()
|
||||
}
|
||||
|
||||
func (f *Forwarder) SetMaxTemporalLayer(temporalLayer int32) (bool, buffer.VideoLayer, buffer.VideoLayer) {
|
||||
func (f *Forwarder) SetMaxTemporalLayer(temporalLayer int32) (bool, buffer.VideoLayer) {
|
||||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
|
||||
if f.kind == webrtc.RTPCodecTypeAudio {
|
||||
return false, buffer.InvalidLayer, buffer.InvalidLayer
|
||||
return false, buffer.InvalidLayer
|
||||
}
|
||||
|
||||
existingMax := f.vls.GetMax()
|
||||
if temporalLayer == existingMax.Temporal {
|
||||
return false, existingMax, f.vls.GetCurrent()
|
||||
return false, existingMax
|
||||
}
|
||||
|
||||
f.logger.Debugw("setting max temporal layer", "layer", temporalLayer)
|
||||
@@ -484,7 +486,7 @@ func (f *Forwarder) SetMaxTemporalLayer(temporalLayer int32) (bool, buffer.Video
|
||||
|
||||
f.clearParkedLayer()
|
||||
|
||||
return true, f.vls.GetMax(), f.vls.GetCurrent()
|
||||
return true, f.vls.GetMax()
|
||||
}
|
||||
|
||||
func (f *Forwarder) MaxLayer() buffer.VideoLayer {
|
||||
@@ -508,6 +510,25 @@ func (f *Forwarder) TargetLayer() buffer.VideoLayer {
|
||||
return f.vls.GetTarget()
|
||||
}
|
||||
|
||||
func (f *Forwarder) GetMaxSubscribedSpatial() int32 {
|
||||
f.lock.RLock()
|
||||
defer f.lock.RUnlock()
|
||||
|
||||
layer := buffer.InvalidLayerSpatial // covers muted case
|
||||
if !f.muted {
|
||||
layer = f.vls.GetMax().Spatial
|
||||
|
||||
// If current is higher, mark the current layer as max subscribed layer
|
||||
// to prevent the current layer from stopping before forwarder switches
|
||||
// to the new and lower max layer,
|
||||
if layer < f.vls.GetCurrent().Spatial {
|
||||
layer = f.vls.GetCurrent().Spatial
|
||||
}
|
||||
}
|
||||
|
||||
return layer
|
||||
}
|
||||
|
||||
func (f *Forwarder) GetReferenceLayerSpatial() int32 {
|
||||
f.lock.RLock()
|
||||
defer f.lock.RUnlock()
|
||||
@@ -1477,103 +1498,176 @@ func (f *Forwarder) GetTranslationParams(extPkt *buffer.ExtPacket, layer int32)
|
||||
return nil, ErrUnknownKind
|
||||
}
|
||||
|
||||
func (f *Forwarder) processSourceSwitch(extPkt *buffer.ExtPacket, layer int32) error {
|
||||
if !f.started {
|
||||
f.started = true
|
||||
f.referenceLayerSpatial = layer
|
||||
f.rtpMunger.SetLastSnTs(extPkt)
|
||||
f.codecMunger.SetLast(extPkt)
|
||||
f.logger.Infow(
|
||||
"starting forwarding",
|
||||
"sequenceNumber", extPkt.Packet.SequenceNumber,
|
||||
"timestamp", extPkt.Packet.Timestamp,
|
||||
"layer", layer,
|
||||
"referenceLayerSpatial", f.referenceLayerSpatial,
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
if f.referenceLayerSpatial == buffer.InvalidLayerSpatial {
|
||||
// on a resume, reference layer may not be set, so only set when it is invalid
|
||||
f.referenceLayerSpatial = layer
|
||||
}
|
||||
|
||||
// Compute how much time passed between the previous forwarded packet
|
||||
// and the current incoming (to be forwarded) packet and calculate
|
||||
// timestamp offset on source change.
|
||||
//
|
||||
// There are three timestamps to consider here
|
||||
// 1. lastTS -> timestamp of last sent packet
|
||||
// 2. refTS -> timestamp of this packet (after munging) calculated using feed's RTCP sender report
|
||||
// 3. expectedTS -> expected timestamp of this packet calculated based on elapsed time since first packet
|
||||
// Ideally, refTS and expectedTS should be very close and lastTS should be before both of those.
|
||||
// But, cases like muting/unmuting, clock vagaries, pacing, etc. make them not satisfy those conditions always.
|
||||
lastTS := f.rtpMunger.GetLast().LastTS
|
||||
refTS := lastTS
|
||||
expectedTS := lastTS
|
||||
switchingAt := time.Now()
|
||||
if f.getReferenceLayerRTPTimestamp != nil {
|
||||
ts, err := f.getReferenceLayerRTPTimestamp(extPkt.Packet.Timestamp, layer, f.referenceLayerSpatial)
|
||||
if err == nil {
|
||||
refTS = ts
|
||||
}
|
||||
// AVSYNC-TODO: can error out here if refTS is not available. It can happen when there is no sender report
|
||||
// for the layer being switched to. Can especially happen at the start of the track when layer switches are
|
||||
// potentially happening very quickly. Erroring out and waiting for a layer for which a sender report has been
|
||||
// received will calculate a better offset, but may result in initial adaptation to take a bit longer depending
|
||||
// on how often publisher/remote side sends RTCP sender report.
|
||||
}
|
||||
|
||||
if f.getExpectedRTPTimestamp != nil {
|
||||
tsExt, err := f.getExpectedRTPTimestamp(switchingAt)
|
||||
if err == nil {
|
||||
expectedTS = uint32(tsExt)
|
||||
} else {
|
||||
rtpDiff := uint32(0)
|
||||
if !f.preStartTime.IsZero() && f.refTSOffset == 0 {
|
||||
timeSinceFirst := time.Since(f.preStartTime)
|
||||
rtpDiff = uint32(timeSinceFirst.Nanoseconds() * int64(f.codec.ClockRate) / 1e9)
|
||||
f.refTSOffset = f.firstTS + rtpDiff - refTS
|
||||
f.logger.Infow(
|
||||
"calculating refTSOffset",
|
||||
"preStartTime", f.preStartTime.String(),
|
||||
"firstTS", f.firstTS,
|
||||
"timeSinceFirst", timeSinceFirst,
|
||||
"rtpDiff", rtpDiff,
|
||||
"refTS", refTS,
|
||||
"refTSOffset", f.refTSOffset,
|
||||
)
|
||||
}
|
||||
expectedTS += rtpDiff
|
||||
}
|
||||
}
|
||||
refTS += f.refTSOffset
|
||||
|
||||
var nextTS uint32
|
||||
if f.lastSSRC == 0 {
|
||||
// If resuming (e. g. on unmute), keep next timestamp close to expected timestamp.
|
||||
//
|
||||
// Rationale:
|
||||
// Case 1: If mute is implemented via something like stopping a track and resuming it on unmute,
|
||||
// the RTP timestamp may not have jumped across mute valley. In this case, old timestamp
|
||||
// should not be used.
|
||||
//
|
||||
// Case 2: OTOH, something like pacing may be adding latency in the publisher path (even if
|
||||
// the timestamps incremented correctly across the mute valley). In this case, reference
|
||||
// timestamp should be used as things will catch up to real time when channel capacity
|
||||
// increases and pacer starts sending at faster rate.
|
||||
//
|
||||
// But, the challenege is distinguishing between the two cases. As a compromise, the difference
|
||||
// between expectedTS and refTS is thresholded. Difference below the threshold is treated as Case 2
|
||||
// and above as Case 1.
|
||||
//
|
||||
// In the event of refTS > expectedTS, another threshold is used to pick the next timestamp.
|
||||
// Ideally, refTS should not be ahead of expectedTS, but expectedTS uses the first packet's
|
||||
// wall clock time. So, if the first packet experienced abmormal latency, it is possible
|
||||
// for refTS > expectedTS
|
||||
diffSeconds := float64(expectedTS-refTS) / float64(f.codec.ClockRate)
|
||||
if diffSeconds >= 0.0 {
|
||||
if diffSeconds > ResumeBehindThresholdSeconds {
|
||||
f.logger.Infow("resume, reference too far behind", "expectedTS", expectedTS, "refTS", refTS, "diffSeconds", diffSeconds)
|
||||
nextTS = expectedTS
|
||||
} else {
|
||||
nextTS = refTS
|
||||
}
|
||||
} else {
|
||||
if math.Abs(diffSeconds) > SwitchAheadThresholdSeconds {
|
||||
f.logger.Infow("resume, reference too far ahead", "expectedTS", expectedTS, "refTS", refTS, "diffSeconds", math.Abs(diffSeconds))
|
||||
nextTS = expectedTS
|
||||
} else {
|
||||
nextTS = refTS
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// switching between layers, check if refTS is too far behind the last sent
|
||||
diffSeconds := float64(refTS-lastTS) / float64(f.codec.ClockRate)
|
||||
if diffSeconds < 0.0 {
|
||||
if math.Abs(diffSeconds) > LayerSwitchBehindThresholdSeconds {
|
||||
// AVSYNC-TODO: This could be due to pacer trickling out this layer. Should potentially return error here and wait for a more opportune time
|
||||
// or some forcing function (like "have waited for too long for layer switch, nothing available, switch to whatever is available" kind of condition)
|
||||
// to do the switch. Just logging it for now.
|
||||
f.logger.Infow("layer switch, reference too far behind", "expectedTS", expectedTS, "refTS", refTS, "lastTS", lastTS, "diffSeconds", math.Abs(diffSeconds))
|
||||
}
|
||||
// use a nominal increase to ensure that timestamp is always moving forward
|
||||
nextTS = lastTS + 1
|
||||
} else {
|
||||
diffSeconds = float64(expectedTS-refTS) / float64(f.codec.ClockRate)
|
||||
if diffSeconds < 0.0 && math.Abs(diffSeconds) > SwitchAheadThresholdSeconds {
|
||||
f.logger.Infow("layer switch, reference too far ahead", "expectedTS", expectedTS, "refTS", refTS, "diffSeconds", math.Abs(diffSeconds))
|
||||
nextTS = expectedTS
|
||||
} else {
|
||||
nextTS = refTS
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if nextTS-lastTS == 0 || nextTS-lastTS > (1<<31) {
|
||||
f.logger.Infow("next timestamp is before last, adjusting", "nextTS", nextTS, "lastTS", lastTS)
|
||||
// nominal increase
|
||||
nextTS = lastTS + 1
|
||||
}
|
||||
f.logger.Infow(
|
||||
"next timestamp on switch",
|
||||
"switchingAt", switchingAt.String(),
|
||||
"layer", layer,
|
||||
"lastTS", lastTS,
|
||||
"refTS", refTS,
|
||||
"refTSOffset", f.refTSOffset,
|
||||
"referenceLayerSpatial", f.referenceLayerSpatial,
|
||||
"expectedTS", expectedTS,
|
||||
"nextTS", nextTS,
|
||||
"jump", nextTS-lastTS,
|
||||
)
|
||||
|
||||
f.rtpMunger.UpdateSnTsOffsets(extPkt, 1, nextTS-lastTS)
|
||||
f.codecMunger.UpdateOffsets(extPkt)
|
||||
return nil
|
||||
}
|
||||
|
||||
// should be called with lock held
|
||||
func (f *Forwarder) getTranslationParamsCommon(extPkt *buffer.ExtPacket, layer int32, tp *TranslationParams) (*TranslationParams, error) {
|
||||
if tp == nil {
|
||||
tp = &TranslationParams{}
|
||||
}
|
||||
if f.lastSSRC != extPkt.Packet.SSRC {
|
||||
if !f.started {
|
||||
f.started = true
|
||||
f.referenceLayerSpatial = layer
|
||||
f.rtpMunger.SetLastSnTs(extPkt)
|
||||
f.codecMunger.SetLast(extPkt)
|
||||
f.logger.Infow(
|
||||
"starting forwarding",
|
||||
"sequenceNumber", extPkt.Packet.SequenceNumber,
|
||||
"timestamp", extPkt.Packet.Timestamp,
|
||||
"layer", layer,
|
||||
"referenceLayerSpatial", f.referenceLayerSpatial,
|
||||
)
|
||||
} else {
|
||||
if f.referenceLayerSpatial == buffer.InvalidLayerSpatial {
|
||||
// on a resume, reference layer may not be set, so only set when it is invalid
|
||||
f.referenceLayerSpatial = layer
|
||||
}
|
||||
|
||||
// Compute how much time passed between the old RTP extPkt
|
||||
// and the current packet, and fix timestamp on source change
|
||||
//
|
||||
// There are three time stamps to consider here
|
||||
// 1. lastTS -> time stamp of last sent packet
|
||||
// 2. refTS -> time stamp of this packet (after munging) calculated using feed's RTCP sender report
|
||||
// 3. expectedTS -> time stamp of this packet (after munging) calculated using this stream's RTCP sender report
|
||||
// Ideally, refTS and expectedTS should be very close and lastTS should be before both of those.
|
||||
// But, cases like muting/unmuting, clock vagaries make them not satisfy those conditions always.
|
||||
//
|
||||
// There are 6 orderings to consider (considering only inequalities). Resolve them using following rules
|
||||
// 1. Timestamp has to move forward
|
||||
// 2. Keep next time stamp close to expected
|
||||
lastTS := f.rtpMunger.GetLast().LastTS
|
||||
refTS := lastTS
|
||||
expectedTS := lastTS
|
||||
minTS := ^uint64(0)
|
||||
switchingAt := time.Now()
|
||||
if f.getReferenceLayerRTPTimestamp != nil {
|
||||
ts, err := f.getReferenceLayerRTPTimestamp(extPkt.Packet.Timestamp, layer, f.referenceLayerSpatial)
|
||||
if err == nil {
|
||||
refTS = ts
|
||||
}
|
||||
}
|
||||
if f.getExpectedRTPTimestamp != nil {
|
||||
ts, min, err := f.getExpectedRTPTimestamp(switchingAt)
|
||||
if err == nil {
|
||||
expectedTS = ts
|
||||
minTS = min
|
||||
} else {
|
||||
rtpDiff := uint32(0)
|
||||
if !f.preStartTime.IsZero() && f.refTSOffset == 0 {
|
||||
timeSinceFirst := time.Since(f.preStartTime)
|
||||
rtpDiff = uint32(timeSinceFirst.Nanoseconds() * int64(f.codec.ClockRate) / 1e9)
|
||||
f.refTSOffset = f.firstTS + rtpDiff - refTS
|
||||
f.logger.Infow(
|
||||
"calculating refTSOffset",
|
||||
"preStartTime", f.preStartTime.String(),
|
||||
"firstTS", f.firstTS,
|
||||
"timeSinceFirst", timeSinceFirst,
|
||||
"rtpDiff", rtpDiff,
|
||||
"refTS", refTS,
|
||||
"refTSOffset", f.refTSOffset,
|
||||
)
|
||||
}
|
||||
expectedTS += rtpDiff
|
||||
}
|
||||
}
|
||||
refTS += f.refTSOffset
|
||||
nextTS, explain := getNextTimestamp(lastTS, refTS, expectedTS, minTS)
|
||||
f.logger.Infow(
|
||||
"next timestamp on switch",
|
||||
"switchingAt", switchingAt.String(),
|
||||
"layer", layer,
|
||||
"lastTS", lastTS,
|
||||
"refTS", refTS,
|
||||
"refTSOffset", f.refTSOffset,
|
||||
"referenceLayerSpatial", f.referenceLayerSpatial,
|
||||
"expectedTS", expectedTS,
|
||||
"minTS", minTS,
|
||||
"nextTS", nextTS,
|
||||
"jump", nextTS-lastTS,
|
||||
"explanation", explain,
|
||||
)
|
||||
|
||||
f.rtpMunger.UpdateSnTsOffsets(extPkt, 1, nextTS-lastTS)
|
||||
f.codecMunger.UpdateOffsets(extPkt)
|
||||
if err := f.processSourceSwitch(extPkt, layer); err != nil {
|
||||
tp.shouldDrop = true
|
||||
return tp, err
|
||||
}
|
||||
|
||||
f.logger.Debugw("switching feed", "from", f.lastSSRC, "to", extPkt.Packet.SSRC)
|
||||
f.lastSSRC = extPkt.Packet.SSRC
|
||||
}
|
||||
|
||||
if tp == nil {
|
||||
tp = &TranslationParams{}
|
||||
}
|
||||
tpRTP, err := f.rtpMunger.UpdateAndGetSnTs(extPkt)
|
||||
if err != nil {
|
||||
tp.shouldDrop = true
|
||||
@@ -1620,9 +1714,7 @@ func (f *Forwarder) getTranslationParamsVideo(extPkt *buffer.ExtPacket, layer in
|
||||
return tp, nil
|
||||
}
|
||||
tp.isResuming = result.IsResuming
|
||||
tp.isSwitchingToRequestSpatial = result.IsSwitchingToRequestSpatial
|
||||
tp.isSwitchingToMaxSpatial = result.IsSwitchingToMaxSpatial
|
||||
tp.maxSpatialLayer = result.MaxSpatialLayer
|
||||
tp.isSwitching = result.IsSwitching
|
||||
tp.ddBytes = result.DependencyDescriptorExtension
|
||||
tp.marker = result.RTPMarker
|
||||
|
||||
@@ -1696,7 +1788,7 @@ func (f *Forwarder) maybeStart() {
|
||||
Packet: &rtp.Packet{
|
||||
Header: rtp.Header{
|
||||
SequenceNumber: uint16(rand.Intn(1<<14)) + uint16(1<<15), // a random number in third quartile of sequence number space
|
||||
Timestamp: uint32(rand.Intn(1<<30)) + uint32(1<<31), // a random number in third quartile of time stamp space
|
||||
Timestamp: uint32(rand.Intn(1<<30)) + uint32(1<<31), // a random number in third quartile of timestamp space
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -1741,16 +1833,16 @@ func (f *Forwarder) GetSnTsForBlankFrames(frameRate uint32, numPackets int) ([]S
|
||||
|
||||
lastTS := f.rtpMunger.GetLast().LastTS
|
||||
expectedTS := lastTS
|
||||
minTS := ^uint64(0)
|
||||
if f.getExpectedRTPTimestamp != nil {
|
||||
ts, min, err := f.getExpectedRTPTimestamp(time.Now())
|
||||
tsExt, err := f.getExpectedRTPTimestamp(time.Now())
|
||||
if err == nil {
|
||||
expectedTS = ts
|
||||
minTS = min
|
||||
expectedTS = uint32(tsExt)
|
||||
}
|
||||
}
|
||||
nextTS, _ := getNextTimestamp(lastTS, expectedTS, expectedTS, minTS)
|
||||
snts, err := f.rtpMunger.UpdateAndGetPaddingSnTs(numPackets, f.codec.ClockRate, frameRate, frameEndNeeded, nextTS)
|
||||
if expectedTS-lastTS == 0 || expectedTS-lastTS > (1<<31) {
|
||||
expectedTS = lastTS + 1
|
||||
}
|
||||
snts, err := f.rtpMunger.UpdateAndGetPaddingSnTs(numPackets, f.codec.ClockRate, frameRate, frameEndNeeded, expectedTS)
|
||||
return snts, frameEndNeeded, err
|
||||
}
|
||||
|
||||
@@ -1885,47 +1977,20 @@ done:
|
||||
if !targetLayer.IsValid() {
|
||||
distance += (maxSeenLayer.Temporal + 1)
|
||||
}
|
||||
// TODO-REMOVE-AFTER-DEBUG
|
||||
logger.Debugw(
|
||||
"distance to desired",
|
||||
"maxSeenLauer", maxSeenLayer,
|
||||
"availableLayers", availableLayers,
|
||||
"brs", brs,
|
||||
"targetLayer", targetLayer,
|
||||
"maxLayer", maxLayer,
|
||||
"adjustedMaxLayer", adjustedMaxLayer,
|
||||
"maxAvailableSpatial", maxAvailableSpatial,
|
||||
"maxAvailableTemporal", maxAvailableTemporal,
|
||||
"distance", distance,
|
||||
"distanceToDesired", float64(distance)/float64(maxSeenLayer.Temporal+1),
|
||||
)
|
||||
|
||||
return float64(distance) / float64(maxSeenLayer.Temporal+1)
|
||||
}
|
||||
|
||||
func getNextTimestamp(lastTS uint32, refTS uint32, expectedTS uint32, minTS uint64) (uint32, string) {
|
||||
isInOrder := func(val1, val2 uint32) bool {
|
||||
diff := val1 - val2
|
||||
return diff != 0 && diff < (1<<31)
|
||||
}
|
||||
|
||||
rl := isInOrder(refTS, lastTS)
|
||||
el := isInOrder(expectedTS, lastTS)
|
||||
er := isInOrder(expectedTS, refTS)
|
||||
|
||||
nextTS := lastTS + 1
|
||||
explain := "l = r = e"
|
||||
|
||||
switch {
|
||||
case rl && el && er: // lastTS < refTS < expectedTS
|
||||
nextTS = uint32(float64(refTS) + 0.05*float64(expectedTS-refTS))
|
||||
explain = fmt.Sprintf("l < r < e, %d, %d", refTS-lastTS, expectedTS-refTS)
|
||||
case rl && el && !er: // lastTS < expectedTS < refTS
|
||||
nextTS = uint32(float64(expectedTS) + 0.5*float64(refTS-expectedTS))
|
||||
explain = fmt.Sprintf("l < e < r, %d, %d", expectedTS-lastTS, refTS-expectedTS)
|
||||
case !rl && el && er: // refTS < lastTS < expectedTS
|
||||
nextTS = uint32(float64(lastTS) + 0.5*float64(expectedTS-lastTS))
|
||||
explain = fmt.Sprintf("r < l < e, %d, %d", lastTS-refTS, expectedTS-lastTS)
|
||||
case !rl && !el && er: // refTS < expectedTS < lastTS
|
||||
nextTS = lastTS + 1
|
||||
explain = fmt.Sprintf("r < e < l, %d, %d", expectedTS-refTS, lastTS-expectedTS)
|
||||
case rl && !el && !er: // expectedTS < lastTS < refTS
|
||||
nextTS = uint32(float64(lastTS) + 0.75*float64(refTS-lastTS))
|
||||
explain = fmt.Sprintf("e < l < r, %d, %d", lastTS-expectedTS, refTS-lastTS)
|
||||
case !rl && !el && !er: // expectedTS < refTS < lastTS
|
||||
nextTS = lastTS + 1
|
||||
explain = fmt.Sprintf("e < r < l, %d, %d", refTS-expectedTS, lastTS-refTS)
|
||||
}
|
||||
|
||||
if minTS != ^uint64(0) && !isInOrder(nextTS, uint32(minTS)) {
|
||||
nextTS = uint32(minTS) + 1
|
||||
}
|
||||
|
||||
return nextTS, explain
|
||||
}
|
||||
|
||||
+13
-21
@@ -26,13 +26,13 @@ func newForwarder(codec webrtc.RTPCodecCapability, kind webrtc.RTPCodecType) *Fo
|
||||
func TestForwarderMute(t *testing.T) {
|
||||
f := newForwarder(testutils.TestOpusCodec, webrtc.RTPCodecTypeAudio)
|
||||
require.False(t, f.IsMuted())
|
||||
muted, _ := f.Mute(false)
|
||||
muted := f.Mute(false)
|
||||
require.False(t, muted) // no change in mute state
|
||||
require.False(t, f.IsMuted())
|
||||
muted, _ = f.Mute(true)
|
||||
muted = f.Mute(true)
|
||||
require.True(t, muted)
|
||||
require.True(t, f.IsMuted())
|
||||
muted, _ = f.Mute(false)
|
||||
muted = f.Mute(false)
|
||||
require.True(t, muted)
|
||||
require.False(t, f.IsMuted())
|
||||
}
|
||||
@@ -45,15 +45,13 @@ func TestForwarderLayersAudio(t *testing.T) {
|
||||
require.Equal(t, buffer.InvalidLayer, f.CurrentLayer())
|
||||
require.Equal(t, buffer.InvalidLayer, f.TargetLayer())
|
||||
|
||||
changed, maxLayer, currentLayer := f.SetMaxSpatialLayer(1)
|
||||
changed, maxLayer := f.SetMaxSpatialLayer(1)
|
||||
require.False(t, changed)
|
||||
require.Equal(t, buffer.InvalidLayer, maxLayer)
|
||||
require.Equal(t, buffer.InvalidLayer, currentLayer)
|
||||
|
||||
changed, maxLayer, currentLayer = f.SetMaxTemporalLayer(1)
|
||||
changed, maxLayer = f.SetMaxTemporalLayer(1)
|
||||
require.False(t, changed)
|
||||
require.Equal(t, buffer.InvalidLayer, maxLayer)
|
||||
require.Equal(t, buffer.InvalidLayer, currentLayer)
|
||||
|
||||
require.Equal(t, buffer.InvalidLayer, f.MaxLayer())
|
||||
}
|
||||
@@ -72,12 +70,11 @@ func TestForwarderLayersVideo(t *testing.T) {
|
||||
Spatial: buffer.DefaultMaxLayerSpatial,
|
||||
Temporal: buffer.DefaultMaxLayerTemporal,
|
||||
}
|
||||
changed, maxLayer, currentLayer := f.SetMaxSpatialLayer(buffer.DefaultMaxLayerSpatial)
|
||||
changed, maxLayer := f.SetMaxSpatialLayer(buffer.DefaultMaxLayerSpatial)
|
||||
require.True(t, changed)
|
||||
require.Equal(t, expectedLayers, maxLayer)
|
||||
require.Equal(t, buffer.InvalidLayer, currentLayer)
|
||||
|
||||
changed, maxLayer, currentLayer = f.SetMaxSpatialLayer(buffer.DefaultMaxLayerSpatial - 1)
|
||||
changed, maxLayer = f.SetMaxSpatialLayer(buffer.DefaultMaxLayerSpatial - 1)
|
||||
require.True(t, changed)
|
||||
expectedLayers = buffer.VideoLayer{
|
||||
Spatial: buffer.DefaultMaxLayerSpatial - 1,
|
||||
@@ -85,21 +82,18 @@ func TestForwarderLayersVideo(t *testing.T) {
|
||||
}
|
||||
require.Equal(t, expectedLayers, maxLayer)
|
||||
require.Equal(t, expectedLayers, f.MaxLayer())
|
||||
require.Equal(t, buffer.InvalidLayer, currentLayer)
|
||||
|
||||
f.vls.SetCurrent(buffer.VideoLayer{Spatial: 0, Temporal: 1})
|
||||
changed, maxLayer, currentLayer = f.SetMaxSpatialLayer(buffer.DefaultMaxLayerSpatial - 1)
|
||||
changed, maxLayer = f.SetMaxSpatialLayer(buffer.DefaultMaxLayerSpatial - 1)
|
||||
require.False(t, changed)
|
||||
require.Equal(t, expectedLayers, maxLayer)
|
||||
require.Equal(t, expectedLayers, f.MaxLayer())
|
||||
require.Equal(t, buffer.VideoLayer{Spatial: 0, Temporal: 1}, currentLayer)
|
||||
|
||||
changed, maxLayer, currentLayer = f.SetMaxTemporalLayer(buffer.DefaultMaxLayerTemporal)
|
||||
changed, maxLayer = f.SetMaxTemporalLayer(buffer.DefaultMaxLayerTemporal)
|
||||
require.False(t, changed)
|
||||
require.Equal(t, expectedLayers, maxLayer)
|
||||
require.Equal(t, buffer.VideoLayer{Spatial: 0, Temporal: 1}, currentLayer)
|
||||
|
||||
changed, maxLayer, currentLayer = f.SetMaxTemporalLayer(buffer.DefaultMaxLayerTemporal - 1)
|
||||
changed, maxLayer = f.SetMaxTemporalLayer(buffer.DefaultMaxLayerTemporal - 1)
|
||||
require.True(t, changed)
|
||||
expectedLayers = buffer.VideoLayer{
|
||||
Spatial: buffer.DefaultMaxLayerSpatial - 1,
|
||||
@@ -107,7 +101,6 @@ func TestForwarderLayersVideo(t *testing.T) {
|
||||
}
|
||||
require.Equal(t, expectedLayers, maxLayer)
|
||||
require.Equal(t, expectedLayers, f.MaxLayer())
|
||||
require.Equal(t, buffer.VideoLayer{Spatial: 0, Temporal: 1}, currentLayer)
|
||||
}
|
||||
|
||||
func TestForwarderAllocateOptimal(t *testing.T) {
|
||||
@@ -1404,8 +1397,8 @@ func TestForwarderGetTranslationParamsVideo(t *testing.T) {
|
||||
marshalledVP8, err := expectedVP8.Marshal()
|
||||
require.NoError(t, err)
|
||||
expectedTP = TranslationParams{
|
||||
isSwitchingToMaxSpatial: true,
|
||||
isResuming: true,
|
||||
isSwitching: true,
|
||||
isResuming: true,
|
||||
rtp: &TranslationParamsRTP{
|
||||
snOrdering: SequenceNumberOrderingContiguous,
|
||||
sequenceNumber: 23333,
|
||||
@@ -1716,8 +1709,7 @@ func TestForwarderGetTranslationParamsVideo(t *testing.T) {
|
||||
marshalledVP8, err = expectedVP8.Marshal()
|
||||
require.NoError(t, err)
|
||||
expectedTP = TranslationParams{
|
||||
isSwitchingToMaxSpatial: true,
|
||||
maxSpatialLayer: 1,
|
||||
isSwitching: true,
|
||||
rtp: &TranslationParamsRTP{
|
||||
snOrdering: SequenceNumberOrderingContiguous,
|
||||
sequenceNumber: 23339,
|
||||
|
||||
+3
-3
@@ -66,7 +66,7 @@ type TrackReceiver interface {
|
||||
|
||||
GetTemporalLayerFpsForSpatial(layer int32) []float32
|
||||
|
||||
GetRTCPSenderReportData(layer int32) (*buffer.RTCPSenderReportData, *buffer.RTCPSenderReportData)
|
||||
GetCalculatedClockRate(layer int32) uint32
|
||||
GetReferenceLayerRTPTimestamp(ts uint32, layer int32, referenceLayer int32) (uint32, error)
|
||||
}
|
||||
|
||||
@@ -752,8 +752,8 @@ func (w *WebRTCReceiver) GetTemporalLayerFpsForSpatial(layer int32) []float32 {
|
||||
return b.GetTemporalLayerFpsForSpatial(layer)
|
||||
}
|
||||
|
||||
func (w *WebRTCReceiver) GetRTCPSenderReportData(layer int32) (*buffer.RTCPSenderReportData, *buffer.RTCPSenderReportData) {
|
||||
return w.streamTrackerManager.GetRTCPSenderReportData(layer)
|
||||
func (w *WebRTCReceiver) GetCalculatedClockRate(layer int32) uint32 {
|
||||
return w.streamTrackerManager.GetCalculatedClockRate(layer)
|
||||
}
|
||||
|
||||
func (w *WebRTCReceiver) GetReferenceLayerRTPTimestamp(ts uint32, layer int32, referenceLayer int32) (uint32, error) {
|
||||
|
||||
@@ -550,6 +550,10 @@ func (s *StreamAllocator) postEvent(event Event) {
|
||||
|
||||
func (s *StreamAllocator) processEvents() {
|
||||
for event := range s.eventCh {
|
||||
if s.isStopped.Load() {
|
||||
break
|
||||
}
|
||||
|
||||
s.handleEvent(&event)
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package sfu
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"sort"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -15,6 +16,14 @@ import (
|
||||
"github.com/livekit/protocol/logger"
|
||||
)
|
||||
|
||||
const (
|
||||
senderReportThresholdSeconds = float64(60.0)
|
||||
|
||||
minDurationForClockRateCalculation = 15 * time.Second
|
||||
)
|
||||
|
||||
// ---------------------------------------------------
|
||||
|
||||
type StreamTrackerManagerListener interface {
|
||||
OnAvailableLayersChanged()
|
||||
OnBitrateAvailabilityChanged()
|
||||
@@ -24,9 +33,12 @@ type StreamTrackerManagerListener interface {
|
||||
OnBitrateReport(availableLayers []int32, bitrates Bitrates)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------
|
||||
|
||||
type endsSenderReport struct {
|
||||
first *buffer.RTCPSenderReportData
|
||||
newest *buffer.RTCPSenderReportData
|
||||
first *buffer.RTCPSenderReportData
|
||||
newest *buffer.RTCPSenderReportData
|
||||
lastUpdated time.Time
|
||||
}
|
||||
|
||||
type StreamTrackerManager struct {
|
||||
@@ -50,6 +62,7 @@ type StreamTrackerManager struct {
|
||||
|
||||
senderReportMu sync.RWMutex
|
||||
senderReports [buffer.DefaultMaxLayerSpatial + 1]endsSenderReport
|
||||
layerOffsets [buffer.DefaultMaxLayerSpatial + 1][buffer.DefaultMaxLayerSpatial + 1]uint32
|
||||
|
||||
closed core.Fuse
|
||||
|
||||
@@ -517,6 +530,40 @@ func (s *StreamTrackerManager) maxExpectedLayerFromTrackInfo() {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *StreamTrackerManager) updateLayerOffsetLocked(ref, other int32) {
|
||||
srRef := s.senderReports[ref].newest
|
||||
srOther := s.senderReports[other].newest
|
||||
if srRef == nil || srRef.NTPTimestamp == 0 || srOther == nil || srOther.NTPTimestamp == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
ntpDiff := srRef.NTPTimestamp.Time().Sub(srOther.NTPTimestamp.Time())
|
||||
if math.Abs(ntpDiff.Seconds()) > senderReportThresholdSeconds {
|
||||
// offset is updated only if the layers' sender reports are close enough.
|
||||
//
|
||||
// Rationale: higher layers could be paused for extended periods of time
|
||||
// due to adaptive stream/dynacast or publisher constraints like CPU/bandwidth.
|
||||
// The check is to avoid using very old reports.
|
||||
return
|
||||
}
|
||||
rtpDiff := ntpDiff.Nanoseconds() * int64(s.clockRate) / 1e9
|
||||
|
||||
// calculate other layer's time stamp at the same time as ref layer's NTP time
|
||||
normalizedOtherTS := srOther.RTPTimestamp + uint32(rtpDiff)
|
||||
|
||||
// now both layers' time stamp refer to the same NTP time and the diff is the offset between the layers
|
||||
offset := srRef.RTPTimestamp - normalizedOtherTS
|
||||
|
||||
// use minimal offset to indicate value availability in the extremely unlikely case of
|
||||
// both layers using the same timestamp
|
||||
if offset == 0 {
|
||||
s.logger.Infow("using default offset", "ref", ref, "other", other)
|
||||
offset = 1
|
||||
}
|
||||
|
||||
s.layerOffsets[ref][other] = offset
|
||||
}
|
||||
|
||||
func (s *StreamTrackerManager) SetRTCPSenderReportData(layer int32, srFirst *buffer.RTCPSenderReportData, srNewest *buffer.RTCPSenderReportData) {
|
||||
s.senderReportMu.Lock()
|
||||
defer s.senderReportMu.Unlock()
|
||||
@@ -527,74 +574,66 @@ func (s *StreamTrackerManager) SetRTCPSenderReportData(layer int32, srFirst *buf
|
||||
|
||||
s.senderReports[layer].first = srFirst
|
||||
s.senderReports[layer].newest = srNewest
|
||||
s.senderReports[layer].lastUpdated = time.Now()
|
||||
|
||||
// (re)fill offsets as necessary for received layer.
|
||||
for i := int32(0); i < buffer.DefaultMaxLayerSpatial+1; i++ {
|
||||
if i == layer {
|
||||
continue
|
||||
}
|
||||
|
||||
// treating layer for which report was received as reference layer
|
||||
s.updateLayerOffsetLocked(layer, i)
|
||||
|
||||
// and the other way
|
||||
s.updateLayerOffsetLocked(i, layer)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *StreamTrackerManager) GetRTCPSenderReportData(layer int32) (*buffer.RTCPSenderReportData, *buffer.RTCPSenderReportData) {
|
||||
func (s *StreamTrackerManager) GetCalculatedClockRate(layer int32) uint32 {
|
||||
s.senderReportMu.RLock()
|
||||
defer s.senderReportMu.RUnlock()
|
||||
|
||||
if layer < 0 || int(layer) >= len(s.senderReports) {
|
||||
return nil, nil
|
||||
// invalid layer
|
||||
return 0
|
||||
}
|
||||
|
||||
return s.senderReports[layer].first, s.senderReports[layer].newest
|
||||
srFirst := s.senderReports[layer].first
|
||||
srNewest := s.senderReports[layer].newest
|
||||
if srFirst == nil || srFirst.NTPTimestamp == 0 || srNewest == nil || srNewest.NTPTimestamp == 0 || srFirst.RTPTimestamp == srNewest.RTPTimestamp {
|
||||
// sender reports invalid or same
|
||||
return 0
|
||||
}
|
||||
|
||||
if s.senderReports[layer].lastUpdated.IsZero() || time.Since(s.senderReports[layer].lastUpdated).Seconds() > senderReportThresholdSeconds {
|
||||
// sender report updated too far back
|
||||
return 0
|
||||
}
|
||||
|
||||
tsf := srNewest.NTPTimestamp.Time().Sub(srFirst.NTPTimestamp.Time())
|
||||
if tsf < minDurationForClockRateCalculation {
|
||||
// not enough time has elapsed to get a stable clock rate calculation
|
||||
return 0
|
||||
}
|
||||
|
||||
rdsf := srNewest.RTPTimestampExt - srFirst.RTPTimestampExt
|
||||
return uint32(float64(rdsf) / tsf.Seconds())
|
||||
}
|
||||
|
||||
func (s *StreamTrackerManager) GetReferenceLayerRTPTimestamp(ts uint32, layer int32, referenceLayer int32) (uint32, error) {
|
||||
s.senderReportMu.RLock()
|
||||
defer s.senderReportMu.RUnlock()
|
||||
|
||||
if layer < 0 || referenceLayer < 0 {
|
||||
if layer < 0 || int(layer) >= len(s.layerOffsets[0]) || referenceLayer < 0 || int(referenceLayer) >= len(s.layerOffsets) {
|
||||
return 0, fmt.Errorf("invalid layer, target: %d, reference: %d", layer, referenceLayer)
|
||||
}
|
||||
|
||||
if layer == referenceLayer {
|
||||
return ts, nil
|
||||
if layer != referenceLayer && s.layerOffsets[referenceLayer][layer] == 0 {
|
||||
return 0, fmt.Errorf("offset unavailable, target: %d, reference: %d", layer, referenceLayer)
|
||||
}
|
||||
|
||||
var srLayer *buffer.RTCPSenderReportData
|
||||
if int(layer) < len(s.senderReports) {
|
||||
srLayer = s.senderReports[layer].newest
|
||||
}
|
||||
if srLayer == nil || srLayer.NTPTimestamp == 0 {
|
||||
return 0, fmt.Errorf("layer rtcp sender report not available: %d", layer)
|
||||
}
|
||||
|
||||
var srRef *buffer.RTCPSenderReportData
|
||||
if int(referenceLayer) < len(s.senderReports) {
|
||||
srRef = s.senderReports[referenceLayer].newest
|
||||
}
|
||||
if srRef == nil || srRef.NTPTimestamp == 0 {
|
||||
return 0, fmt.Errorf("reference layer rtcp sender report not available: %d", referenceLayer)
|
||||
}
|
||||
|
||||
// line up the RTP time stamps using NTP time of most recent sender report of layer and referenceLayer
|
||||
// NOTE: It is possible that reference layer has stopped (due to dynacast/adaptive streaming OR publisher
|
||||
// constraints). It should be okay even if the layer has stopped for a long time when using modulo arithmetic for
|
||||
// RTP time stamp (uint32 arithmetic).
|
||||
ntpDiff := srRef.NTPTimestamp.Time().Sub(srLayer.NTPTimestamp.Time())
|
||||
rtpDiff := ntpDiff.Nanoseconds() * int64(s.clockRate) / 1e9
|
||||
normalizedTS := srLayer.RTPTimestamp + uint32(rtpDiff)
|
||||
s.logger.Infow(
|
||||
"getting reference timestamp",
|
||||
"layer", layer,
|
||||
"referenceLayer", referenceLayer,
|
||||
"incomingTS", ts,
|
||||
"layerNTP", srLayer.NTPTimestamp.Time().String(),
|
||||
"refNTP", srRef.NTPTimestamp.Time().String(),
|
||||
"ntpDiff", ntpDiff.String(),
|
||||
"layerRTP", srLayer.RTPTimestamp,
|
||||
"refRTP", srRef.RTPTimestamp,
|
||||
"rtpDiff", rtpDiff,
|
||||
"normalizedTS", normalizedTS,
|
||||
"mappedTS", ts+(srRef.RTPTimestamp-normalizedTS),
|
||||
)
|
||||
|
||||
// now that both RTP timestamps correspond to roughly the same NTP time,
|
||||
// the diff between them is the offset in RTP timestamp units between layer and referenceLayer.
|
||||
// Add the offset to layer's ts to map it to corresponding RTP timestamp in
|
||||
// the reference layer.
|
||||
return ts + (srRef.RTPTimestamp - normalizedTS), nil
|
||||
return ts + s.layerOffsets[referenceLayer][layer], nil
|
||||
}
|
||||
|
||||
func (s *StreamTrackerManager) GetMaxTemporalLayerSeen() int32 {
|
||||
|
||||
@@ -204,26 +204,6 @@ func (d *DependencyDescriptor) Select(extPkt *buffer.ExtPacket, _layer int32) (r
|
||||
|
||||
d.previousActiveDecodeTargetsBitmask = d.activeDecodeTargetsBitmask
|
||||
d.activeDecodeTargetsBitmask = buffer.GetActiveDecodeTargetBitmask(d.currentLayer, ddwdt.DecodeTargets)
|
||||
|
||||
if d.currentLayer.Spatial == d.requestSpatial {
|
||||
result.IsSwitchingToRequestSpatial = true
|
||||
}
|
||||
if d.currentLayer.Spatial == d.maxLayer.Spatial {
|
||||
result.IsSwitchingToMaxSpatial = true
|
||||
result.MaxSpatialLayer = d.currentLayer.Spatial
|
||||
d.logger.Infow(
|
||||
"reached max layer",
|
||||
"previous", d.previousLayer,
|
||||
"current", d.currentLayer,
|
||||
"previousTarget", d.previousTargetLayer,
|
||||
"target", d.targetLayer,
|
||||
"max", d.maxLayer,
|
||||
"layer", fd.SpatialId,
|
||||
"req", d.requestSpatial,
|
||||
"maxSeen", d.maxSeenLayer,
|
||||
"feed", extPkt.Packet.SSRC,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
ddExtension := &dede.DependencyDescriptorExtension{
|
||||
|
||||
@@ -26,25 +26,15 @@ func (s *Simulcast) IsOvershootOkay() bool {
|
||||
}
|
||||
|
||||
func (s *Simulcast) Select(extPkt *buffer.ExtPacket, layer int32) (result VideoLayerSelectorResult) {
|
||||
populateSwitches := func(isActive bool, reason string) {
|
||||
result.IsSwitching = true
|
||||
populateSwitches := func(isSwitching bool, isActive bool, reason string) {
|
||||
if isSwitching {
|
||||
result.IsSwitching = true
|
||||
}
|
||||
|
||||
if !isActive {
|
||||
result.IsResuming = true
|
||||
}
|
||||
|
||||
if s.currentLayer.Spatial == s.requestSpatial {
|
||||
result.IsSwitchingToRequestSpatial = true
|
||||
}
|
||||
|
||||
if s.currentLayer.Spatial >= s.maxLayer.Spatial {
|
||||
result.IsSwitchingToMaxSpatial = true
|
||||
result.MaxSpatialLayer = s.currentLayer.Spatial
|
||||
if reason != "" {
|
||||
reason += ", "
|
||||
}
|
||||
reason += "reached max layer"
|
||||
}
|
||||
|
||||
if reason != "" {
|
||||
s.logger.Infow(
|
||||
reason,
|
||||
@@ -70,6 +60,7 @@ func (s *Simulcast) Select(extPkt *buffer.ExtPacket, layer int32) (result VideoL
|
||||
// 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
|
||||
isActive := s.currentLayer.IsValid()
|
||||
found := false
|
||||
reason := ""
|
||||
@@ -77,6 +68,7 @@ func (s *Simulcast) Select(extPkt *buffer.ExtPacket, layer int32) (result VideoL
|
||||
if s.parkedLayer.Spatial == layer {
|
||||
reason = "resuming at parked layer"
|
||||
currentLayer = s.parkedLayer
|
||||
isSwitching = false
|
||||
found = true
|
||||
}
|
||||
} else {
|
||||
@@ -110,7 +102,7 @@ func (s *Simulcast) Select(extPkt *buffer.ExtPacket, layer int32) (result VideoL
|
||||
s.targetLayer.Spatial = s.currentLayer.Spatial
|
||||
}
|
||||
|
||||
populateSwitches(isActive, reason)
|
||||
populateSwitches(isSwitching, isActive, reason)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,7 +116,7 @@ func (s *Simulcast) Select(extPkt *buffer.ExtPacket, layer int32) (result VideoL
|
||||
s.targetLayer.Spatial = layer
|
||||
}
|
||||
|
||||
populateSwitches(true, "adjusting overshoot")
|
||||
populateSwitches(true, true, "adjusting overshoot")
|
||||
}
|
||||
|
||||
result.RTPMarker = extPkt.Packet.Marker
|
||||
|
||||
@@ -10,9 +10,6 @@ type VideoLayerSelectorResult struct {
|
||||
IsRelevant bool
|
||||
IsSwitching bool
|
||||
IsResuming bool
|
||||
IsSwitchingToRequestSpatial bool
|
||||
IsSwitchingToMaxSpatial bool
|
||||
MaxSpatialLayer int32
|
||||
RTPMarker bool
|
||||
DependencyDescriptorExtension []byte
|
||||
}
|
||||
|
||||
@@ -80,26 +80,6 @@ func (v *VP9) Select(extPkt *buffer.ExtPacket, _layer int32) (result VideoLayerS
|
||||
result.IsResuming = true
|
||||
}
|
||||
|
||||
if v.currentLayer.Spatial != v.requestSpatial && updatedLayer.Spatial == v.requestSpatial {
|
||||
result.IsSwitchingToRequestSpatial = true
|
||||
}
|
||||
|
||||
if v.currentLayer.Spatial != v.maxLayer.Spatial && updatedLayer.Spatial == v.maxLayer.Spatial {
|
||||
result.IsSwitchingToMaxSpatial = true
|
||||
result.MaxSpatialLayer = updatedLayer.Spatial
|
||||
v.logger.Infow(
|
||||
"reached max layer",
|
||||
"current", v.currentLayer,
|
||||
"updated", updatedLayer,
|
||||
"target", v.targetLayer,
|
||||
"max", v.maxLayer,
|
||||
"layer", extPkt.VideoLayer.Spatial,
|
||||
"req", v.requestSpatial,
|
||||
"maxSeen", v.maxSeenLayer,
|
||||
"feed", extPkt.Packet.SSRC,
|
||||
)
|
||||
}
|
||||
|
||||
v.previousLayer = v.currentLayer
|
||||
v.currentLayer = updatedLayer
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user